/*-------------------------------------------------------------------------
 *
 * setrefs.c
 *	  完成计划树的后处理：修复对子计划变量的引用，计算操作符的 regproc 值等。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/plan/setrefs.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/transam.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/tlist.h"
#include "tcop/utility.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"


typedef struct
{
	int			varno;			/* Var 的 RT 索引 */
	AttrNumber	varattno;		/* Var 的属性编号 */
	AttrNumber	resno;			/* Var 的 TLE 位置 */
} tlist_vinfo;

typedef struct
{
	List	   *tlist;			/* 基础目标列表 */
	int			num_vars;		/* 普通 Var tlist 条目的数量 */
	bool		has_ph_vars;	/* 是否存在 PlaceHolderVar 条目？ */
	bool		has_non_vars;	/* 是否存在其他条目？ */
	tlist_vinfo vars[FLEXIBLE_ARRAY_MEMBER];	/* 具有 num_vars 条目 */
} indexed_tlist;

typedef struct
{
	PlannerInfo *root;
	int			rtoffset;
	double		num_exec;
} fix_scan_expr_context;

typedef struct
{
	PlannerInfo *root;
	indexed_tlist *outer_itlist;
	indexed_tlist *inner_itlist;
	Index		acceptable_rel;
	int			rtoffset;
	double		num_exec;
} fix_join_expr_context;

typedef struct
{
	PlannerInfo *root;
	indexed_tlist *subplan_itlist;
	int			newvarno;
	int			rtoffset;
	double		num_exec;
} fix_upper_expr_context;

typedef struct
{
	PlannerInfo *root;
	indexed_tlist *subplan_itlist;
	int			newvarno;
} fix_windowagg_cond_context;

/*
 * 在 AlternativeSubPlan 表达式中选择最佳替代方案需要
 * 估计该表达式将被评估多少次。对于计划节点的目标列表中的
 * 表达式，计划的预估输出行数显然是我们需要使用的，但对于
 * qual 中的表达式则不太明确。由于 AlternativeSubPlans 不常使用，
 * 我们不想花费太多周期来进行这样的估计。我们使用的是输出行数的两倍。
 * 这并非毫无根据：我们知道 clause_selectivity() 对任何 SubPlan
 * 将回退到 0.5 的默认选择性估计，因此如果包含 SubPlan 的 qual
 * 是最后一个被应用（根据 order_qual_clauses() 这很可能发生），
 * 这与你可能以更繁琐的方式估计的结果相匹配。显然还有许多其他场景，
 * 但尝试改善此估计可能不值得，尤其是在我们没有更好估计 SubPlan
 * qual 自身的选择性时。
 */
#define NUM_EXEC_TLIST(parentplan)  ((parentplan)->plan_rows)
#define NUM_EXEC_QUAL(parentplan)   ((parentplan)->plan_rows * 2.0)

/*
 * 检查 Const 节点是否为 regclass 值。我们也接受普通 OID，
 * 因为如果 regclass Const 是 oideq 或类似运算符的参数，
 * 它将折叠为该类型。（这可能会导致计划的关系依赖列表中
 * 出现一些多余的值，但最糟糕的结果将是偶尔的无用重新规划。）
 */
#define ISREGCLASSCONST(con) \
	(((con)->consttype == REGCLASSOID || (con)->consttype == OIDOID) && \
	 !(con)->constisnull)

#define fix_scan_list(root, lst, rtoffset, num_exec) \
	((List *) fc_fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))

static void add_rtes_to_flat_rtable(PlannerInfo *fc_root, bool fc_recursing);
static void fc_flatten_unplanned_rtes(PlannerGlobal *fc_glob, RangeTblEntry *fc_rte);
static bool fc_flatten_rtes_walker(Node *fc_node, PlannerGlobal *fc_glob);
static void add_rte_to_flat_rtable(PlannerGlobal *fc_glob, RangeTblEntry *fc_rte);
static Plan *set_plan_refs(PlannerInfo *fc_root, Plan *fc_plan, int fc_rtoffset);
static Plan *set_indexonlyscan_references(PlannerInfo *fc_root,
										  IndexOnlyScan *fc_plan,
										  int fc_rtoffset);
static Plan *set_subqueryscan_references(PlannerInfo *fc_root,
										 SubqueryScan *fc_plan,
										 int fc_rtoffset);
static Plan *fc_clean_up_removed_plan_level(Plan *fc_parent, Plan *fc_child);
static void set_foreignscan_references(PlannerInfo *fc_root,
									   ForeignScan *fc_fscan,
									   int fc_rtoffset);
static void set_customscan_references(PlannerInfo *fc_root,
									  CustomScan *fc_cscan,
									  int fc_rtoffset);
static Plan *set_append_references(PlannerInfo *fc_root,
								   Append *fc_aplan,
								   int fc_rtoffset);
static Plan *set_mergeappend_references(PlannerInfo *fc_root,
										MergeAppend *fc_mplan,
										int fc_rtoffset);
static void set_hash_references(PlannerInfo *fc_root, Plan *fc_plan, int fc_rtoffset);
static Relids fc_offset_relid_set(Relids fc_relids, int fc_rtoffset);
static Node *fc_fix_scan_expr(PlannerInfo *fc_root, Node *fc_node,
						   int fc_rtoffset, double fc_num_exec);
static Node *fc_fix_scan_expr_mutator(Node *fc_node, fix_scan_expr_context *fc_context);
static bool fc_fix_scan_expr_walker(Node *fc_node, fix_scan_expr_context *fc_context);
static void set_join_references(PlannerInfo *fc_root, Join *fc_join, int fc_rtoffset);
static void set_upper_references(PlannerInfo *fc_root, Plan *fc_plan, int fc_rtoffset);
static void set_param_references(PlannerInfo *fc_root, Plan *fc_plan);
static Node *fc_convert_combining_aggrefs(Node *fc_node, void *fc_context);
static void set_dummy_tlist_references(Plan *fc_plan, int fc_rtoffset);
static indexed_tlist *fc_build_tlist_index(List *fc_tlist);
static Var *fc_search_indexed_tlist_for_var(Var *fc_var,
										 indexed_tlist *fc_itlist,
										 int fc_newvarno,
										 int fc_rtoffset);
static Var *fc_search_indexed_tlist_for_non_var(Expr *fc_node,
											 indexed_tlist *fc_itlist,
											 int fc_newvarno);
static Var *fc_search_indexed_tlist_for_sortgroupref(Expr *fc_node,
												  Index fc_sortgroupref,
												  indexed_tlist *fc_itlist,
												  int fc_newvarno);
static List *fc_fix_join_expr(PlannerInfo *fc_root,
						   List *fc_clauses,
						   indexed_tlist *fc_outer_itlist,
						   indexed_tlist *fc_inner_itlist,
						   Index fc_acceptable_rel,
						   int fc_rtoffset, double fc_num_exec);
static Node *fc_fix_join_expr_mutator(Node *fc_node,
								   fix_join_expr_context *fc_context);
static Node *fc_fix_upper_expr(PlannerInfo *fc_root,
							Node *fc_node,
							indexed_tlist *fc_subplan_itlist,
							int fc_newvarno,
							int fc_rtoffset, double fc_num_exec);
static Node *fc_fix_upper_expr_mutator(Node *fc_node,
									fix_upper_expr_context *fc_context);
static List *set_returning_clause_references(PlannerInfo *fc_root,
											 List *fc_rlist,
											 Plan *fc_topplan,
											 Index fc_resultRelation,
											 int fc_rtoffset);
static List *set_windowagg_runcondition_references(PlannerInfo *fc_root,
												   List *fc_runcondition,
												   Plan *fc_plan);


/*****************************************************************************
 *
 *		子计划引用
 *
 *****************************************************************************/


/*
 * set_plan_references
 *
 * 这是规划器/优化器的最终处理阶段。计划
 * 树已完整；我们只需调整一些表示细节
 * 以便于执行器：
 *
 * 1. 我们将各种子查询的 rangetables 扁平化为一个单一的列表，并
 * 将执行器不需要的 RangeTblEntry 字段置零。
 *
 * 2. 我们调整扫描节点中的 Vars 以与扁平的 rangetable 一致。
 *
 * 3. 我们调整上层计划节点中的 Vars 以引用其
 * 子计划的输出。
 *
 * 4. 在涉及
 * 部分聚合或最小最大聚合优化的情况下，需要调整 Agg 计划节点中的 Aggrefs。
 *
 * 5. PARAM_MULTIEXPR 参数被常规的 PARAM_EXEC 参数替代，
 * 现在我们已经完成了所有 MULTIEXPR 子计划的规划。
 *
 * 6. AlternativeSubPlan 表达式被替换为其
 * 一种替代方案，使用估计的执行次数。
 *
 * 7. 我们计算操作符的 regproc OID（即我们查找每个操作符的实现函数）。
 *
 * 8. 我们创建计划依赖的特定对象列表。
 * 这将被 plancache.c 用于驱动缓存计划的失效。
 * 关系依赖用 OID 表示，其他一切用
 * PlanInvalItems 表示（这种区分是由于共享失效 API 的需求）。
 * 目前，关系、用户定义函数和域是唯一
 * 以这种方式被明确跟踪的对象类型。
 *
 * 9. 我们为树中的每个计划节点分配一个唯一的 ID。
 *
 * 我们还进行最后一步优化，即删除
 * 不执行任何有用操作的 SubqueryScan、Append 和 MergeAppend 计划节点。
 * 这样做的原因是：
 * 在 set_plan_references 之前不能轻易完成这一点，因为它会
 * 破坏 set_upper_references：子计划顶层 tlist 中的 Vars
 * 将无法与外层计划树中的 Vars 对应。SubqueryScan
 * 在外部查询和子查询变量编号之间作为一个缓冲区
 * 具有必要功能……但在我们扁平化 rangetable 后这不再是问题，
 * 因为此时只有一个 rtindex 命名空间。
 * 同样，Append 和 MergeAppend 在附加关系的父变量和子变量之间进行缓冲，
 * 但在完成 set_plan_references 后我们不需要担心这一点。
 *
 * set_plan_references 递归遍历整个计划树。
 *
 * 返回值通常是传入的相同计划节点，但在传入的计划是我们认为不需要的节点时可能会不同。
 *
 * 扁平化的 rangetable 条目被附加到 root->glob->finalrtable。
 * 此外，rowmarks 条目被附加到 root->glob->finalrowmarks， 
 * ModifyTable 结果关系的 RT 索引被附加到 root->glob->resultRelations， 
 * 扁平化的 AppendRelInfos 被附加到 root->glob->appendRelations。
 * 计划依赖被附加到 root->glob->relationOids（针对关系）
 * 和 root->glob->invalItems（针对其他所有内容）。
 *
 * 请注意，我们就地修改计划节点，但使用 expression_tree_mutator
 * 来处理目标列表和条件表达式。我们可以假设计划
 * 节点是刚由规划器构建的且没有被多重引用，但对于表达式树节点而言，这并不是那么安全的假设。
 */
Plan * set_plan_references(PlannerInfo *fc_root, Plan *fc_plan)
{
	Plan	   *fc_result;
	PlannerGlobal *fc_glob = fc_root->glob;
	int			fc_rtoffset = list_length(fc_glob->finalrtable);
	ListCell   *lc;

	/*
	 * 将所有查询的 RTE 添加到扁平化的 rangetable 中。活动 RTE
	 * 的 rangetable 索引将通过 rtoffset 增加。 （额外的
	 * RTE，未被计划树引用，可能会在其后添加。）
	 */
	add_rtes_to_flat_rtable(fc_root, false);

	/*
	 * 调整 PlanRowMarks 的 RT 索引并添加到最终 rowmarks 列表
	 */
	foreach(lc, fc_root->rowMarks)
	{
		PlanRowMark *fc_rc = lfirst_node(PlanRowMark, lc);
		PlanRowMark *fc_newrc;

		/* 扁平副本就足够了，因为所有字段都是标量 */
		fc_newrc = (PlanRowMark *) palloc(sizeof(PlanRowMark));
		memcpy(fc_newrc, fc_rc, sizeof(PlanRowMark));

		/* 调整索引……但 *不* 调整 rowmarkId */
		fc_newrc->rti += fc_rtoffset;
		fc_newrc->prti += fc_rtoffset;

		fc_glob->finalrowmarks = lappend(fc_glob->finalrowmarks, fc_newrc);
	}

	/*
	 * 调整 AppendRelInfos 的 RT 索引并添加到最终的 appendrels 列表。
	 * 我们假设 AppendRelInfos 是在规划过程中构建的，不需要
	 * 复制。
	 */
	foreach(lc, fc_root->append_rel_list)
	{
		AppendRelInfo *fc_appinfo = lfirst_node(AppendRelInfo, lc);

		/* 调整 RT 索引 */
		fc_appinfo->parent_relid += fc_rtoffset;
		fc_appinfo->child_relid += fc_rtoffset;

		/*
		 * 与其调整 translated_vars 条目，不如直接丢弃它们。
		 * 执行器和 EXPLAIN 目前不需要这些数据。
		 */
		fc_appinfo->translated_vars = NIL;

		fc_glob->appendRelations = lappend(fc_glob->appendRelations, fc_appinfo);
	}

	/* 如有必要，为处理 AlternativeSubPlans 创建工作区 */
	if (fc_root->hasAlternativeSubPlans)
	{
		fc_root->isAltSubplan = (bool *)
			palloc0(list_length(fc_glob->subplans) * sizeof(bool));
		fc_root->isUsedSubplan = (bool *)
			palloc0(list_length(fc_glob->subplans) * sizeof(bool));
	}

	/* 现在修复计划树 */
	fc_result = set_plan_refs(fc_root, fc_plan, fc_rtoffset);

	/*
	 * 如果我们有 AlternativeSubPlans，可能会在 glob->subplans 中有一些
	 * 未引用的子计划。为避免在稍后处理这些子计划时浪费资源，
	 * 通过将这些列表条目设置为 NULL 来去除它们。 （注意：一旦处理了
	 * AlternativeSubPlan，我们不能立刻这样做，因为可能有多个
	 * AlternativeSubPlan 的副本，它们的解决方式可能不同。）
	 */
	if (fc_root->hasAlternativeSubPlans)
	{
		foreach(lc, fc_glob->subplans)
		{
			int			fc_ndx = foreach_current_index(lc);

			/*
			 * 如果它在这个查询层级中被某些 AlternativeSubPlan 使用过，
			 * 但并没有被任何 AlternativeSubPlan 选为最佳，那么我们
			 * 就不需要它。 不要触碰不属于 AlternativeSubPlans 的子计划。
			 */
			if (fc_root->isAltSubplan[fc_ndx] && !fc_root->isUsedSubplan[fc_ndx])
				lfirst(lc) = NULL;
		}
	}

	return fc_result;
}

/*
 * 从计划的 rangetable 中提取 RangeTblEntries，并添加到平面 rtable
 *
 * 这可以递归到子查询计划；如果是这样，“递归”即为真。
 */
static void add_rtes_to_flat_rtable(PlannerInfo *fc_root, bool fc_recursing)
{
	PlannerGlobal *fc_glob = fc_root->glob;
	Index		fc_rti;
	ListCell   *fc_lc;

	/*
	 * 将查询自己的 RTEs 添加到扁平化的 rangetable。
	 *
	 * 在顶层，我们必须添加所有 RTEs，以便它们在扁平化
	 * rangetable 中的索引与它们的原始索引匹配。 当递归时，
	 * 我们只关心提取关系 RTEs。
	 */
	foreach(fc_lc, fc_root->parse->rtable)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_lc);

		if (!fc_recursing || fc_rte->rtekind == RTE_RELATION)
			add_rte_to_flat_rtable(fc_glob, fc_rte);
	}

	/*
	 * 如果有任何死子查询，它们在计划树中没有被引用，
	 * 所以我们必须单独将其中包含的 RTEs 添加到扁平化的 rtable。
	 * （如果我们没有这样做，执行器将不会对提到此类
	 * 子查询的表执行预期的权限检查。）
	 *
	 * 注意：对 rangetable 的这一遍历不能与前一个
	 * 合并，因为那样会搞乱扁平化 rangetable 中的
	 * 活 RTEs 的编号。
	 */
	fc_rti = 1;
	foreach(fc_lc, fc_root->parse->rtable)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_lc);

		/*
		 * 我们应该忽略继承父 RTEs：它们的内容已经被
		 * 提升到我们的 rangetable 中。 也忽略没有匹配
		 * RelOptInfos 的任何子查询 RTEs，因为它们同样
		 * 被提升。
		 */
		if (fc_rte->rtekind == RTE_SUBQUERY && !fc_rte->inh &&
			fc_rti < fc_root->simple_rel_array_size)
		{
			RelOptInfo *fc_rel = fc_root->simple_rel_array[fc_rti];

			if (fc_rel != NULL)
			{
				Assert(fc_rel->relid == fc_rti);	/* 对数组进行完整性检查 */

				/*
				 * 子查询可能根本就没有被计划过，如果它
				 * 因为自相矛盾的约束在我们的查询层级中被排除。
				 * 在这种情况下应用 flatten_unplanned_rtes。
				 *
				 * 如果它被计划过但结果关系是虚拟的，我们假设
				 * 它已经从我们的计划树中省略（见 set_subquery_pathlist），
				 * 并递归以提取它的 RTEs。
				 *
				 * 否则，它应该由计划树中的某个地方的
				 * SubqueryScan 节点表示，当我们处理该计划
				 * 节点时会提取它的 RTEs。
				 *
				 * 然而，如果我们正在递归，那么我们应该提取 RTEs
				 * 无论子查询是虚拟的还是非虚拟的，因为我们发现
				 * 一些上层查询层将这个视为虚拟的，
				 * 所以我们根本不会扫描这一层的计划树。
				 */
				if (fc_rel->subroot == NULL)
					fc_flatten_unplanned_rtes(fc_glob, fc_rte);
				else if (fc_recursing ||
						 IS_DUMMY_REL(fetch_upper_rel(fc_rel->subroot,
													  UPPERREL_FINAL, NULL)))
					add_rtes_to_flat_rtable(fc_rel->subroot, true);
			}
		}
		fc_rti++;
	}
}

/*
 * 从根本未计划的子查询中提取 RangeTblEntries
 */
static void fc_flatten_unplanned_rtes(PlannerGlobal *fc_glob, RangeTblEntry *fc_rte)
{
	/* 使用 query_tree_walker 查找解析树中的所有 RTEs */
	(void) query_tree_walker(fc_rte->subquery,
							 fc_flatten_rtes_walker,
							 (void *) fc_glob,
							 QTW_EXAMINE_RTES_BEFORE);
}

static bool fc_flatten_rtes_walker(Node *fc_node, PlannerGlobal *fc_glob)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, RangeTblEntry))
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) fc_node;

		/* 与上面一样，我们只需保存关系 RTEs */
		if (fc_rte->rtekind == RTE_RELATION)
			add_rte_to_flat_rtable(fc_glob, fc_rte);
		return false;
	}
	if (IsA(fc_node, Query))
	{
		/* 递归进入子选择 */
		return query_tree_walker((Query *) fc_node,
								 fc_flatten_rtes_walker,
								 (void *) fc_glob,
								 QTW_EXAMINE_RTES_BEFORE);
	}
	return expression_tree_walker(fc_node, fc_flatten_rtes_walker,
								  (void *) fc_glob);
}

/*
 * 将给定的 RTE（副本）添加到最终的 rangetable
 *
 * 在平面 rangetable 中，我们将不需要的子结构指针置为零；
 * 这减少了存储空间和缓存计划的复制成本。我们只保留 ctename、
 * alias 和 eref Alias 字段，这些是 EXPLAIN 所需的，以及 selectedCols、
 * insertedCols、updatedCols 和 extraUpdatedCols 位图，这些是
 * 执行器启动权限检查和触发事件检查所需的。
 */
static void add_rte_to_flat_rtable(PlannerGlobal *fc_glob, RangeTblEntry *fc_rte)
{
	RangeTblEntry *fc_newrte;

	/* 扁平副本以复制所有标量字段 */
	fc_newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
	memcpy(fc_newrte, fc_rte, sizeof(RangeTblEntry));

	/* 删除不需要的子结构 */
	fc_newrte->tablesample = NULL;
	fc_newrte->subquery = NULL;
	fc_newrte->joinaliasvars = NIL;
	fc_newrte->joinleftcols = NIL;
	fc_newrte->joinrightcols = NIL;
	fc_newrte->join_using_alias = NULL;
	fc_newrte->functions = NIL;
	fc_newrte->tablefunc = NULL;
	fc_newrte->values_lists = NIL;
	fc_newrte->coltypes = NIL;
	fc_newrte->coltypmods = NIL;
	fc_newrte->colcollations = NIL;
	fc_newrte->securityQuals = NIL;

	fc_glob->finalrtable = lappend(fc_glob->finalrtable, fc_newrte);

	/*
	 * 如果它是普通的关系 RTE，则将表添加到 relationOids。
	 *
	 * 尽管该 RTE 可能在计划树中未被引用；这对应于
	 * 诸如已展开的视图、通过约束排除而消除的子表等情况。
	 * 对于这样的关系，模式失效仍然必须强制重建计划。
	 *
	 * 注意，我们不愿意避免生成重复的列表条目。
	 * 我们可以这样做，但这可能会消耗比节省更多的周期。
	 */
	if (fc_newrte->rtekind == RTE_RELATION)
		fc_glob->relationOids = lappend_oid(fc_glob->relationOids, fc_newrte->relid);
}

/*
 * set_plan_refs：递归遍历单个子查询层级的计划节点
 */
static Plan * set_plan_refs(PlannerInfo *fc_root, Plan *fc_plan, int fc_rtoffset)
{
	ListCell   *fc_l;

	if (fc_plan == NULL)
		return NULL;

	/* 为该节点分配一个唯一 ID。 */
	fc_plan->plan_node_id = fc_root->glob->lastPlanNodeId++;

	/*
	 * 特定于计划类型的修复
	 */
	switch (nodeTag(fc_plan))
	{
		case T_SeqScan:
			{
				SeqScan    *fc_splan = (SeqScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
			}
			break;
		case T_SampleScan:
			{
				SampleScan *fc_splan = (SampleScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				fc_splan->tablesample = (TableSampleClause *)
					fc_fix_scan_expr(fc_root, (Node *) fc_splan->tablesample,
								  fc_rtoffset, 1);
			}
			break;
		case T_IndexScan:
			{
				IndexScan  *fc_splan = (IndexScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				fc_splan->indexqual =
					fix_scan_list(fc_root, fc_splan->indexqual,
								  fc_rtoffset, 1);
				fc_splan->indexqualorig =
					fix_scan_list(fc_root, fc_splan->indexqualorig,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				fc_splan->indexorderby =
					fix_scan_list(fc_root, fc_splan->indexorderby,
								  fc_rtoffset, 1);
				fc_splan->indexorderbyorig =
					fix_scan_list(fc_root, fc_splan->indexorderbyorig,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
			}
			break;
		case T_IndexOnlyScan:
			{
				IndexOnlyScan *fc_splan = (IndexOnlyScan *) fc_plan;

				return set_indexonlyscan_references(fc_root, fc_splan, fc_rtoffset);
			}
			break;
		case T_BitmapIndexScan:
			{
				BitmapIndexScan *fc_splan = (BitmapIndexScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				/* 无需修复 targetlist 和 qual */
				Assert(fc_splan->scan.plan.targetlist == NIL);
				Assert(fc_splan->scan.plan.qual == NIL);
				fc_splan->indexqual =
					fix_scan_list(fc_root, fc_splan->indexqual, fc_rtoffset, 1);
				fc_splan->indexqualorig =
					fix_scan_list(fc_root, fc_splan->indexqualorig,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
			}
			break;
		case T_BitmapHeapScan:
			{
				BitmapHeapScan *fc_splan = (BitmapHeapScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				fc_splan->bitmapqualorig =
					fix_scan_list(fc_root, fc_splan->bitmapqualorig,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
			}
			break;
		case T_TidScan:
			{
				TidScan    *fc_splan = (TidScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				fc_splan->tidquals =
					fix_scan_list(fc_root, fc_splan->tidquals,
								  fc_rtoffset, 1);
			}
			break;
		case T_TidRangeScan:
			{
				TidRangeScan *fc_splan = (TidRangeScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				fc_splan->tidrangequals =
					fix_scan_list(fc_root, fc_splan->tidrangequals,
								  fc_rtoffset, 1);
			}
			break;
		case T_SubqueryScan:
			/* 需要特殊处理，见下面的注释 */
			return set_subqueryscan_references(fc_root,
											   (SubqueryScan *) fc_plan,
											   fc_rtoffset);
		case T_FunctionScan:
			{
				FunctionScan *fc_splan = (FunctionScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				fc_splan->functions =
					fix_scan_list(fc_root, fc_splan->functions, fc_rtoffset, 1);
			}
			break;
		case T_TableFuncScan:
			{
				TableFuncScan *fc_splan = (TableFuncScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				fc_splan->tablefunc = (TableFunc *)
					fc_fix_scan_expr(fc_root, (Node *) fc_splan->tablefunc,
								  fc_rtoffset, 1);
			}
			break;
		case T_ValuesScan:
			{
				ValuesScan *fc_splan = (ValuesScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				fc_splan->values_lists =
					fix_scan_list(fc_root, fc_splan->values_lists,
								  fc_rtoffset, 1);
			}
			break;
		case T_CteScan:
			{
				CteScan    *fc_splan = (CteScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
			}
			break;
		case T_NamedTuplestoreScan:
			{
				NamedTuplestoreScan *fc_splan = (NamedTuplestoreScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
			}
			break;
		case T_WorkTableScan:
			{
				WorkTableScan *fc_splan = (WorkTableScan *) fc_plan;

				fc_splan->scan.scanrelid += fc_rtoffset;
				fc_splan->scan.plan.targetlist =
					fix_scan_list(fc_root, fc_splan->scan.plan.targetlist,
								  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
				fc_splan->scan.plan.qual =
					fix_scan_list(fc_root, fc_splan->scan.plan.qual,
								  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
			}
			break;
		case T_ForeignScan:
			set_foreignscan_references(fc_root, (ForeignScan *) fc_plan, fc_rtoffset);
			break;
		case T_CustomScan:
			set_customscan_references(fc_root, (CustomScan *) fc_plan, fc_rtoffset);
			break;

		case T_NestLoop:
		case T_MergeJoin:
		case T_HashJoin:
			set_join_references(fc_root, (Join *) fc_plan, fc_rtoffset);
			break;

		case T_Gather:
		case T_GatherMerge:
			{
				set_upper_references(fc_root, fc_plan, fc_rtoffset);
				set_param_references(fc_root, fc_plan);
			}
			break;

		case T_Hash:
			set_hash_references(fc_root, fc_plan, fc_rtoffset);
			break;

		case T_Memoize:
			{
				Memoize    *fc_mplan = (Memoize *) fc_plan;

				/*
				 * Memoize 不会评估其 targetlist。 它只使用
				 * 来自其外部子节点的相同 targetlist。
				 */
				set_dummy_tlist_references(fc_plan, fc_rtoffset);

				fc_mplan->param_exprs = fix_scan_list(fc_root, fc_mplan->param_exprs,
												   fc_rtoffset,
												   NUM_EXEC_TLIST(fc_plan));
				break;
			}

		case T_Material:
		case T_Sort:
		case T_IncrementalSort:
		case T_Unique:
		case T_SetOp:

			/*
			 * 这些计划类型实际上不麻烦地评估它们的
			 * targetlists，因为它们只是返回其未修改的输入
			 * 元组。 即使 targetlist 不会被
			 * 执行器使用，我们也会对其进行修正以便可能被
			 * EXPLAIN 使用（更不用说调试的方便——错误的 varnos 会非常混淆）。
			 */
			set_dummy_tlist_references(fc_plan, fc_rtoffset);

			/*
			 * 由于这些计划类型也不检查 quals，我们应该不会
			 * 查找到附加在它们上的任何 qual 表达式。
			 */
			Assert(fc_plan->qual == NIL);
			break;
		case T_LockRows:
			{
				LockRows   *fc_splan = (LockRows *) fc_plan;

				
/*
				 * 像上述计划类型一样，LockRows 不会评估其
				 * tlist 或 quals。但是我们必须修复其行标记中的 RT 索引。
				 */
				set_dummy_tlist_references(fc_plan, fc_rtoffset);
				Assert(fc_splan->plan.qual == NIL);

				foreach(fc_l, fc_splan->rowMarks)
				{
					PlanRowMark *fc_rc = (PlanRowMark *) lfirst(fc_l);

					fc_rc->rti += fc_rtoffset;
					fc_rc->prti += fc_rtoffset;
				}
			}
			break;
		case T_Limit:
			{
				Limit	   *fc_splan = (Limit *) fc_plan;

				/*
				 * 像上述计划类型一样，Limit 不会评估其 tlist
				 * 或 quals。然而，它确实有用于 limit/offset 的实时表达式；
				 * 而且这些表达式不能包含子计划变量引用，因此 fix_scan_expr 可以处理它们。
				 */
				set_dummy_tlist_references(fc_plan, fc_rtoffset);
				Assert(fc_splan->plan.qual == NIL);

				fc_splan->limitOffset =
					fc_fix_scan_expr(fc_root, fc_splan->limitOffset, fc_rtoffset, 1);
				fc_splan->limitCount =
					fc_fix_scan_expr(fc_root, fc_splan->limitCount, fc_rtoffset, 1);
			}
			break;
		case T_Agg:
			{
				Agg		   *fc_agg = (Agg *) fc_plan;

				/*
				 * 如果这个节点正在组合部分聚合结果，我们
				 * 必须将其 Aggrefs 转换为包含对将从子计划节点
				 * 可用的部分聚合子表达式的引用。
				 */
				if (DO_AGGSPLIT_COMBINE(fc_agg->aggsplit))
				{
					fc_plan->targetlist = (List *)
						fc_convert_combining_aggrefs((Node *) fc_plan->targetlist,
												  NULL);
					fc_plan->qual = (List *)
						fc_convert_combining_aggrefs((Node *) fc_plan->qual,
												  NULL);
				}

				set_upper_references(fc_root, fc_plan, fc_rtoffset);
			}
			break;
		case T_Group:
			set_upper_references(fc_root, fc_plan, fc_rtoffset);
			break;
		case T_WindowAgg:
			{
				WindowAgg  *fc_wplan = (WindowAgg *) fc_plan;

				/*
				 * 通过将 WindowAgg 的运行条件调整为交换
				 * WindowFuncs 引用以改为引用扫描槽中的 Var，
				 * 这样当执行器评估运行条件时，它从
				 * 结果刚刚存储的槽中接收 WindowFunc 的值，而不是
				 * 再次评估 WindowFunc。
				 */
				fc_wplan->runCondition = set_windowagg_runcondition_references(fc_root,
																			fc_wplan->runCondition,
																			(Plan *) fc_wplan);

				set_upper_references(fc_root, fc_plan, fc_rtoffset);

				/*
				 * 像 Limit 节点的 limit/offset 表达式一样，WindowAgg 也有
				 * 帧偏移表达式，这些表达式不能包含子计划
				 * 变量引用，因此 fix_scan_expr 可以处理它们。
				 */
				fc_wplan->startOffset =
					fc_fix_scan_expr(fc_root, fc_wplan->startOffset, fc_rtoffset, 1);
				fc_wplan->endOffset =
					fc_fix_scan_expr(fc_root, fc_wplan->endOffset, fc_rtoffset, 1);
				fc_wplan->runCondition = fix_scan_list(fc_root,
													fc_wplan->runCondition,
													fc_rtoffset,
													NUM_EXEC_TLIST(fc_plan));
				fc_wplan->runConditionOrig = fix_scan_list(fc_root,
														fc_wplan->runConditionOrig,
														fc_rtoffset,
														NUM_EXEC_TLIST(fc_plan));
			}
			break;
		case T_Result:
			{
				Result	   *fc_splan = (Result *) fc_plan;

				/*
				 * Result 可能具有也可能没有子计划；如果没有，它更像
				 * 一个扫描节点而不是上层节点。
				 */
				if (fc_splan->plan.lefttree != NULL)
					set_upper_references(fc_root, fc_plan, fc_rtoffset);
				else
				{
					/*
					 * 一个没有子节点的 Result 的 tlist 可能包含
					 * 未解析的 ROWID_VAR Vars，以防它表示一个
					 * 因约束排除而完全为空的目标关系。将任何此类 Vars 替换为 null
					 * 常量，好像它们已被解析为不支持它们的叶子
					 * 扫描节点。我们可以让 fix_scan_expr 做这个，但由于这种情况
					 * 只预期在这里发生，因此在这里特殊处理似乎更安全，并保持
					 * ROWID_VAR 不应该被 fix_scan_expr 看到的断言。
					 */
					foreach(fc_l, fc_splan->plan.targetlist)
					{
						TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);
						Var		   *fc_var = (Var *) fc_tle->expr;

						if (fc_var && IsA(fc_var, Var) && fc_var->varno == ROWID_VAR)
							fc_tle->expr = (Expr *) makeNullConst(fc_var->vartype,
															   fc_var->vartypmod,
															   fc_var->varcollid);
					}

					fc_splan->plan.targetlist =
						fix_scan_list(fc_root, fc_splan->plan.targetlist,
									  fc_rtoffset, NUM_EXEC_TLIST(fc_plan));
					fc_splan->plan.qual =
						fix_scan_list(fc_root, fc_splan->plan.qual,
									  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));
				}
				/* resconstantqual 不能包含任何子计划变量引用 */
				fc_splan->resconstantqual =
					fc_fix_scan_expr(fc_root, fc_splan->resconstantqual, fc_rtoffset, 1);
			}
			break;
		case T_ProjectSet:
			set_upper_references(fc_root, fc_plan, fc_rtoffset);
			break;
		case T_ModifyTable:
			{
				ModifyTable *fc_splan = (ModifyTable *) fc_plan;
				Plan	   *fc_subplan = outerPlan(fc_splan);

				Assert(fc_splan->plan.targetlist == NIL);
				Assert(fc_splan->plan.qual == NIL);

				fc_splan->withCheckOptionLists =
					fix_scan_list(fc_root, fc_splan->withCheckOptionLists,
								  fc_rtoffset, 1);

				if (fc_splan->returningLists)
				{
					List	   *fc_newRL = NIL;
					ListCell   *fc_lcrl,
							   *fc_lcrr;

					/*
					 * 通过调用 set_returning_clause_references() 处理每个 resultrel 返回的列表。
					 */
					Assert(list_length(fc_splan->returningLists) == list_length(fc_splan->resultRelations));
					forboth(fc_lcrl, fc_splan->returningLists,
							fc_lcrr, fc_splan->resultRelations)
					{
						List	   *fc_rlist = (List *) lfirst(fc_lcrl);
						Index		fc_resultrel = lfirst_int(fc_lcrr);

						fc_rlist = set_returning_clause_references(fc_root,
																fc_rlist,
																fc_subplan,
																fc_resultrel,
																fc_rtoffset);
						fc_newRL = lappend(fc_newRL, fc_rlist);
					}
					fc_splan->returningLists = fc_newRL;

					/*
					 * 将可见的计划目标列表设置为与第一个 RETURNING 列表相同。这是为 EXPLAIN 使用的；执行器不会关注目标列表。我们将此步骤推迟到这里，以便不必对相同的目标列表调用 set_returning_clause_references() 两次。
					 */
					fc_splan->plan.targetlist = copyObject(linitial(fc_newRL));
				}

				/*
				 * 我们将带有 ON CONFLICT 的 ModifyTable 视为一种“伪连接”，其中内侧是 EXCLUDED 元组。因此，使用 fix_join_expr 来设置相关变量为 INNER_VAR。我们明确不会创建任何 OUTER_VAR，因为那些已经被 RETURNING 使用，避免冲突似乎更好。
				 */
				if (fc_splan->onConflictSet)
				{
					indexed_tlist *fc_itlist;

					fc_itlist = fc_build_tlist_index(fc_splan->exclRelTlist);

					fc_splan->onConflictSet =
						fc_fix_join_expr(fc_root, fc_splan->onConflictSet,
									  NULL, fc_itlist,
									  linitial_int(fc_splan->resultRelations),
									  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));

					fc_splan->onConflictWhere = (Node *)
						fc_fix_join_expr(fc_root, (List *) fc_splan->onConflictWhere,
									  NULL, fc_itlist,
									  linitial_int(fc_splan->resultRelations),
									  fc_rtoffset, NUM_EXEC_QUAL(fc_plan));

					pfree(fc_itlist);

					fc_splan->exclRelTlist =
						fix_scan_list(fc_root, fc_splan->exclRelTlist, fc_rtoffset, 1);
				}

				/*
				 * MERGE 语句通过在目标关系与源关系（可以是普通关系或子查询）之间执行右连接来生成目标行。MERGE 语句的 INSERT 和 UPDATE 操作需要访问源关系的列。我们安排好事情，以便源关系属性可以作为 INNER_VAR 获得，目标关系属性可以从扫描元组中获得。
				 */
				if (fc_splan->mergeActionLists != NIL)
				{
					ListCell   *fc_lca,
							   *fc_lcr;

					/*
					 * 修复单个操作节点的目标列表，以便所谓的“源关系”变量被引用为 INNER_VAR。注意，为了让执行期间这一点工作正常，ecxt_innertuple 必须设置为通过执行子计划获得的元组，这就是构成“源关系”的内容。
					 *
					 * 我们保持结果关系（即目标关系）中的变量不变，即那些变量将从扫描槽中提取。因此，在执行过程中，我们必须确保 ecxt_scantuple 正确设置为引用目标关系中的元组。
					 */
					indexed_tlist *fc_itlist;

					fc_itlist = fc_build_tlist_index(fc_subplan->targetlist);

					forboth(fc_lca, fc_splan->mergeActionLists,
							fc_lcr, fc_splan->resultRelations)
					{
						List	   *fc_mergeActionList = lfirst(fc_lca);
						Index		fc_resultrel = lfirst_int(fc_lcr);

						foreach(fc_l, fc_mergeActionList)
						{
							MergeAction *fc_action = (MergeAction *) lfirst(fc_l);

							/* 修复每个操作的目标列表。 */
							fc_action->targetList = fc_fix_join_expr(fc_root,
															   fc_action->targetList,
															   NULL, fc_itlist,
															   fc_resultrel,
															   fc_rtoffset,
															   NUM_EXEC_TLIST(fc_plan));

							/* 也要修复条件。 */
							fc_action->qual = (Node *) fc_fix_join_expr(fc_root,
																  (List *) fc_action->qual,
																  NULL, fc_itlist,
																  fc_resultrel,
																  fc_rtoffset,
																  NUM_EXEC_QUAL(fc_plan));
						}
					}
				}

				fc_splan->nominalRelation += fc_rtoffset;
				if (fc_splan->rootRelation)
					fc_splan->rootRelation += fc_rtoffset;
				fc_splan->exclRelRTI += fc_rtoffset;

				foreach(fc_l, fc_splan->resultRelations)
				{
					lfirst_int(fc_l) += fc_rtoffset;
				}
				foreach(fc_l, fc_splan->rowMarks)
				{
					PlanRowMark *fc_rc = (PlanRowMark *) lfirst(fc_l);

					fc_rc->rti += fc_rtoffset;
					fc_rc->prti += fc_rtoffset;
				}

				/*
				 * 将此 ModifyTable 节点的最终结果关系 RT 索引附加到计划的全局列表中。
				 */
				fc_root->glob->resultRelations =
					list_concat(fc_root->glob->resultRelations,
								fc_splan->resultRelations);
				if (fc_splan->rootRelation)
				{
					fc_root->glob->resultRelations =
						lappend_int(fc_root->glob->resultRelations,
									fc_splan->rootRelation);
				}
			}
			break;
		case T_Append:
			/* 需要特殊处理，见下面的注释 */
			return set_append_references(fc_root,
										 (Append *) fc_plan,
										 fc_rtoffset);
		case T_MergeAppend:
			/* 需要特殊处理，见下面的注释 */
			return set_mergeappend_references(fc_root,
											  (MergeAppend *) fc_plan,
											  fc_rtoffset);
		case T_RecursiveUnion:
			/* 这也不会评估目标列表或检查条件 */
			set_dummy_tlist_references(fc_plan, fc_rtoffset);
			Assert(fc_plan->qual == NIL);
			break;
		case T_BitmapAnd:
			{
				BitmapAnd  *fc_splan = (BitmapAnd *) fc_plan;

				/* BitmapAnd 像 Append 一样工作，但没有目标列表 */
				Assert(fc_splan->plan.targetlist == NIL);
				Assert(fc_splan->plan.qual == NIL);
				foreach(fc_l, fc_splan->bitmapplans)
				{
					lfirst(fc_l) = set_plan_refs(fc_root,
											  (Plan *) lfirst(fc_l),
											  fc_rtoffset);
				}
			}
			break;
		case T_BitmapOr:
			{
				BitmapOr   *fc_splan = (BitmapOr *) fc_plan;

				/* BitmapOr 像 Append 一样工作，但没有目标列表 */
				Assert(fc_splan->plan.targetlist == NIL);
				Assert(fc_splan->plan.qual == NIL);
				foreach(fc_l, fc_splan->bitmapplans)
				{
					lfirst(fc_l) = set_plan_refs(fc_root,
											  (Plan *) lfirst(fc_l),
											  fc_rtoffset);
				}
			}
			break;
		default:
			elog(ERROR, "unrecognized node type: %d",
				 (int) nodeTag(fc_plan));
			break;
	}

	/*
	 * 如果有子计划，现在递归进入
	 *
	 * 注意：在我们设置此计划的目标列表和条件中的子计划引用后，再递归进入子计划是至关重要的。如果我们自下而上进行引用调整，那么我们将无法将此计划的变量节点与子节点的已修改节点匹配。
	 */
	fc_plan->lefttree = set_plan_refs(fc_root, fc_plan->lefttree, fc_rtoffset);
	fc_plan->righttree = set_plan_refs(fc_root, fc_plan->righttree, fc_rtoffset);

	return fc_plan;
}

/*
 * set_indexonlyscan_references
 *		对 IndexOnlyScan 进行 set_plan_references 处理
 *
 * 这与普通的 IndexScan 的处理不同，因为我们必须将引用堆的变量转换为引用索引的变量。我们可以使用 fix_upper_expr 机制，通过处理描述索引列的目标列表来实现。
 */
static Plan * set_indexonlyscan_references(PlannerInfo *fc_root,
							 IndexOnlyScan *fc_plan,
							 int fc_rtoffset)
{
	indexed_tlist *fc_index_itlist;
	List	   *fc_stripped_indextlist;
	ListCell   *fc_lc;

	/*
	 * 计划节点的目标列表、条件和重新检查条件中的变量只能引用索引 AM 实际可以返回的列。为确保这一点，从索引的目标列表中删除不可返回的列（标记为 resjunk）。我们可以直接丢弃它们，因为 indexed_tlist 机制关注的是 TLE resnos，而不是物理列表位置。
	 */
	fc_stripped_indextlist = NIL;
	foreach(fc_lc, fc_plan->indextlist)
	{
		TargetEntry *fc_indextle = (TargetEntry *) lfirst(fc_lc);

		if (!fc_indextle->resjunk)
			fc_stripped_indextlist = lappend(fc_stripped_indextlist, fc_indextle);
	}

	fc_index_itlist = fc_build_tlist_index(fc_stripped_indextlist);

	fc_plan->scan.scanrelid += fc_rtoffset;
	fc_plan->scan.plan.targetlist = (List *)
		fc_fix_upper_expr(fc_root,
					   (Node *) fc_plan->scan.plan.targetlist,
					   fc_index_itlist,
					   INDEX_VAR,
					   fc_rtoffset,
					   NUM_EXEC_TLIST((Plan *) fc_plan));
	fc_plan->scan.plan.qual = (List *)
		fc_fix_upper_expr(fc_root,
					   (Node *) fc_plan->scan.plan.qual,
					   fc_index_itlist,
					   INDEX_VAR,
					   fc_rtoffset,
					   NUM_EXEC_QUAL((Plan *) fc_plan));
	fc_plan->recheckqual = (List *)
		fc_fix_upper_expr(fc_root,
					   (Node *) fc_plan->recheckqual,
					   fc_index_itlist,
					   INDEX_VAR,
					   fc_rtoffset,
					   NUM_EXEC_QUAL((Plan *) fc_plan));
	/* indexqual 已经转换为引用索引列 */
	fc_plan->indexqual = fix_scan_list(fc_root, fc_plan->indexqual,
									fc_rtoffset, 1);
	/* indexorderby 已经转换为引用索引列 */
	fc_plan->indexorderby = fix_scan_list(fc_root, fc_plan->indexorderby,
									   fc_rtoffset, 1);
	/* indextlist 绝对不能转换为引用索引列 */
	fc_plan->indextlist = fix_scan_list(fc_root, fc_plan->indextlist,
									 fc_rtoffset, NUM_EXEC_TLIST((Plan *) fc_plan));

	pfree(fc_index_itlist);

	return (Plan *) fc_plan;
}

/*
 * set_subqueryscan_references
 *		对 SubqueryScan 进行 set_plan_references 处理
 *
 * 我们尝试完全剥离 SubqueryScan；如果不能，则必须对此进行正常处理。
 */
static Plan * set_subqueryscan_references(PlannerInfo *fc_root,
							SubqueryScan *fc_plan,
							int fc_rtoffset)
{
	RelOptInfo *fc_rel;
	Plan	   *fc_result;

	/* 需要查找子查询的 RelOptInfo，因为我们需要它的 subroot */
	fc_rel = find_base_rel(fc_root, fc_plan->scan.scanrelid);

	/* 递归处理子计划 */
	fc_plan->subplan = set_plan_references(fc_rel->subroot, fc_plan->subplan);

	if (trivial_subqueryscan(fc_plan))
	{
		/*
		 * 我们可以省略 SubqueryScan 节点，只需提取子计划。
		 */
		fc_result = fc_clean_up_removed_plan_level((Plan *) fc_plan, fc_plan->subplan);
	}
	else
	{
		/*
		 * 保持 SubqueryScan 节点。我们必须处理 set_plan_references 本来会在其上完成的处理。请注意，我们在这里不做 set_upper_references()，因为 SubqueryScan 在开始创建时就会自动与其子计划的输出建立正确的引用。
		 */
		fc_plan->scan.scanrelid += fc_rtoffset;
		fc_plan->scan.plan.targetlist =
			fix_scan_list(fc_root, fc_plan->scan.plan.targetlist,
						  fc_rtoffset, NUM_EXEC_TLIST((Plan *) fc_plan));
		fc_plan->scan.plan.qual =
			fix_scan_list(fc_root, fc_plan->scan.plan.qual,
						  fc_rtoffset, NUM_EXEC_QUAL((Plan *) fc_plan));

		fc_result = (Plan *) fc_plan;
	}

	return fc_result;
}

/*
 * trivial_subqueryscan
 *		检测 SubqueryScan 是否可以从计划树中删除。
 *
 * 如果没有要检查的条件，且目标列表只是在重复子计划的输出，则可以删除它。
 *
 * 这可以从 mark_async_capable_plan() 调用，这是 create_append_plan() 的辅助函数，在 set_subqueryscan_references() 之前调用，以确定作为 Append 节点子项的 SubqueryScan 的琐碎性。所以我们将结果缓存到 SubqueryScan 节点中，以避免重复计算。
 *
 * 注意：从 mark_async_capable_plan() 调用时，我们在对 SubqueryScan 节点（如果需要）运行 finalize_plan() 之前确定结果，并在子计划树上运行 set_plan_references()，但这是安全的，因为 1) finalize_plan() 不会修改 SubqueryScan 节点的 tlist 或 quals（或子计划树中任何计划节点的 tlist 或 quals），并且 2) set_plan_references() 修改子计划树中每个计划节点的 tlist，但保持常量/可回收列作为常量/可回收列，并保持 tlist 的长度和顺序，以及 3) set_plan_references() 可能会从子计划树中删除最上层的计划节点，如 Append 或 MergeAppend，并将子计划节点向上拉，但在这种情况下，子计划节点的 tlist 正好与父级匹配。
 */
bool trivial_subqueryscan(SubqueryScan *fc_plan)
{
	int			fc_attrno;
	ListCell   *fc_lp,
			   *fc_lc;

	/* 我们可能已经检测到这一点；在这种情况下重用结果 */
	if (fc_plan->scanstatus == SUBQUERY_SCAN_TRIVIAL)
		return true;
	if (fc_plan->scanstatus == SUBQUERY_SCAN_NONTRIVIAL)
		return false;
	Assert(fc_plan->scanstatus == SUBQUERY_SCAN_UNKNOWN);
	/* 初始时，将 SubqueryScan 标记为不可删除 */
	fc_plan->scanstatus = SUBQUERY_SCAN_NONTRIVIAL;

	if (fc_plan->scan.plan.qual != NIL)
		return false;

	if (list_length(fc_plan->scan.plan.targetlist) !=
		list_length(fc_plan->subplan->targetlist))
		return false;			/* tlists 不同长度 */

	fc_attrno = 1;
	forboth(fc_lp, fc_plan->scan.plan.targetlist, fc_lc, fc_plan->subplan->targetlist)
	{
		TargetEntry *fc_ptle = (TargetEntry *) lfirst(fc_lp);
		TargetEntry *fc_ctle = (TargetEntry *) lfirst(fc_lc);

		if (fc_ptle->resjunk != fc_ctle->resjunk)
			return false;		/* tlist 不匹配杂项状态 */

		/*
		 * 我们接受引用子计划 tlist 相应元素的 Var，或等于子计划元素的 Const。参见 generate_setop_tlist() 的动机。
		 */
		if (fc_ptle->expr && IsA(fc_ptle->expr, Var))
		{
			Var		   *fc_var = (Var *) fc_ptle->expr;

			Assert(fc_var->varno == fc_plan->scan.scanrelid);
			Assert(fc_var->varlevelsup == 0);
			if (fc_var->varattno != fc_attrno)
				return false;	/* 顺序错误 */
		}
		else if (fc_ptle->expr && IsA(fc_ptle->expr, Const))
		{
			if (!equal(fc_ptle->expr, fc_ctle->expr))
				return false;
		}
		else
			return false;

		fc_attrno++;
	}

	/* 重新将 SubqueryScan 标记为可删除 */
	fc_plan->scanstatus = SUBQUERY_SCAN_TRIVIAL;

	return true;
}

/*
 * clean_up_removed_plan_level
 *		在我们剥离一个 SubqueryScan、Append 等时，执行必要的清理
 *
 * 我们放弃“父”计划，转而仅返回其“子”计划。
 * 需要进行一些小的调整。
 */
static Plan * fc_clean_up_removed_plan_level(Plan *fc_parent, Plan *fc_child)
{
	/*
	 * 我们必须确保不丢失任何 initplans，因此将所有与
	 * 父计划附加的 initplans 移动到子计划。如果我们确实移动了任何内容，子计划
	 * 将不再是并行安全的。
	 */
	if (fc_parent->initPlan)
		fc_child->parallel_safe = false;

	/*
	 * 以这种方式附加计划，以便父计划的 initplans 在
	 * 子计划的任何预先存在的 initplans 之前处理。可能这没什么关系，但
	 * 我们保留这个顺序以防万一。
	 */
	fc_child->initPlan = list_concat(fc_parent->initPlan,
								  fc_child->initPlan);

	/*
	 * 我们还必须将父计划的列标记信息转移到子计划，
	 * 否则如果这是最上层的计划级别，发送给客户端的列将被错误标记。
	 * resjunk 等内容也可能很重要。
	 */
	apply_tlist_labeling(fc_child->targetlist, fc_parent->targetlist);

	return fc_child;
}

/*
 * set_foreignscan_references
 *	   对 ForeignScan 进行 set_plan_references 处理
 */
static void set_foreignscan_references(PlannerInfo *fc_root,
						   ForeignScan *fc_fscan,
						   int fc_rtoffset)
{
	/* 如果有效，则调整 scanrelid */
	if (fc_fscan->scan.scanrelid > 0)
		fc_fscan->scan.scanrelid += fc_rtoffset;

	if (fc_fscan->fdw_scan_tlist != NIL || fc_fscan->scan.scanrelid == 0)
	{
		/*
		 * 调整 tlist、qual、fdw_exprs、fdw_recheck_quals 以引用
		 * 外部扫描元组
		 */
		indexed_tlist *fc_itlist = fc_build_tlist_index(fc_fscan->fdw_scan_tlist);

		fc_fscan->scan.plan.targetlist = (List *)
			fc_fix_upper_expr(fc_root,
						   (Node *) fc_fscan->scan.plan.targetlist,
						   fc_itlist,
						   INDEX_VAR,
						   fc_rtoffset,
						   NUM_EXEC_TLIST((Plan *) fc_fscan));
		fc_fscan->scan.plan.qual = (List *)
			fc_fix_upper_expr(fc_root,
						   (Node *) fc_fscan->scan.plan.qual,
						   fc_itlist,
						   INDEX_VAR,
						   fc_rtoffset,
						   NUM_EXEC_QUAL((Plan *) fc_fscan));
		fc_fscan->fdw_exprs = (List *)
			fc_fix_upper_expr(fc_root,
						   (Node *) fc_fscan->fdw_exprs,
						   fc_itlist,
						   INDEX_VAR,
						   fc_rtoffset,
						   NUM_EXEC_QUAL((Plan *) fc_fscan));
		fc_fscan->fdw_recheck_quals = (List *)
			fc_fix_upper_expr(fc_root,
						   (Node *) fc_fscan->fdw_recheck_quals,
						   fc_itlist,
						   INDEX_VAR,
						   fc_rtoffset,
						   NUM_EXEC_QUAL((Plan *) fc_fscan));
		pfree(fc_itlist);
		/* fdw_scan_tlist 本身只需 fix_scan_list() 调整 */
		fc_fscan->fdw_scan_tlist =
			fix_scan_list(fc_root, fc_fscan->fdw_scan_tlist,
						  fc_rtoffset, NUM_EXEC_TLIST((Plan *) fc_fscan));
	}
	else
	{
		/*
		 * 以标准方式调整 tlist、qual、fdw_exprs、fdw_recheck_quals
		 */
		fc_fscan->scan.plan.targetlist =
			fix_scan_list(fc_root, fc_fscan->scan.plan.targetlist,
						  fc_rtoffset, NUM_EXEC_TLIST((Plan *) fc_fscan));
		fc_fscan->scan.plan.qual =
			fix_scan_list(fc_root, fc_fscan->scan.plan.qual,
						  fc_rtoffset, NUM_EXEC_QUAL((Plan *) fc_fscan));
		fc_fscan->fdw_exprs =
			fix_scan_list(fc_root, fc_fscan->fdw_exprs,
						  fc_rtoffset, NUM_EXEC_QUAL((Plan *) fc_fscan));
		fc_fscan->fdw_recheck_quals =
			fix_scan_list(fc_root, fc_fscan->fdw_recheck_quals,
						  fc_rtoffset, NUM_EXEC_QUAL((Plan *) fc_fscan));
	}

	fc_fscan->fs_relids = fc_offset_relid_set(fc_fscan->fs_relids, fc_rtoffset);

	/* 如果有效，则调整 resultRelation */
	if (fc_fscan->resultRelation > 0)
		fc_fscan->resultRelation += fc_rtoffset;
}

/*
 * set_customscan_references
 *	   对 CustomScan 进行 set_plan_references 处理
 */
static void set_customscan_references(PlannerInfo *fc_root,
						  CustomScan *fc_cscan,
						  int fc_rtoffset)
{
	ListCell   *fc_lc;

	/* 如果有效，则调整 scanrelid */
	if (fc_cscan->scan.scanrelid > 0)
		fc_cscan->scan.scanrelid += fc_rtoffset;

	if (fc_cscan->custom_scan_tlist != NIL || fc_cscan->scan.scanrelid == 0)
	{
		/* 调整 tlist、qual、custom_exprs 以引用自定义扫描元组 */
		indexed_tlist *fc_itlist = fc_build_tlist_index(fc_cscan->custom_scan_tlist);

		fc_cscan->scan.plan.targetlist = (List *)
			fc_fix_upper_expr(fc_root,
						   (Node *) fc_cscan->scan.plan.targetlist,
						   fc_itlist,
						   INDEX_VAR,
						   fc_rtoffset,
						   NUM_EXEC_TLIST((Plan *) fc_cscan));
		fc_cscan->scan.plan.qual = (List *)
			fc_fix_upper_expr(fc_root,
						   (Node *) fc_cscan->scan.plan.qual,
						   fc_itlist,
						   INDEX_VAR,
						   fc_rtoffset,
						   NUM_EXEC_QUAL((Plan *) fc_cscan));
		fc_cscan->custom_exprs = (List *)
			fc_fix_upper_expr(fc_root,
						   (Node *) fc_cscan->custom_exprs,
						   fc_itlist,
						   INDEX_VAR,
						   fc_rtoffset,
						   NUM_EXEC_QUAL((Plan *) fc_cscan));
		pfree(fc_itlist);
		/* custom_scan_tlist 本身只需 fix_scan_list() 调整 */
		fc_cscan->custom_scan_tlist =
			fix_scan_list(fc_root, fc_cscan->custom_scan_tlist,
						  fc_rtoffset, NUM_EXEC_TLIST((Plan *) fc_cscan));
	}
	else
	{
		/* 以标准方式调整 tlist、qual、custom_exprs */
		fc_cscan->scan.plan.targetlist =
			fix_scan_list(fc_root, fc_cscan->scan.plan.targetlist,
						  fc_rtoffset, NUM_EXEC_TLIST((Plan *) fc_cscan));
		fc_cscan->scan.plan.qual =
			fix_scan_list(fc_root, fc_cscan->scan.plan.qual,
						  fc_rtoffset, NUM_EXEC_QUAL((Plan *) fc_cscan));
		fc_cscan->custom_exprs =
			fix_scan_list(fc_root, fc_cscan->custom_exprs,
						  fc_rtoffset, NUM_EXEC_QUAL((Plan *) fc_cscan));
	}

	/* 如果需要，递归调整子计划节点 */
	foreach(fc_lc, fc_cscan->custom_plans)
	{
		lfirst(fc_lc) = set_plan_refs(fc_root, (Plan *) lfirst(fc_lc), fc_rtoffset);
	}

	fc_cscan->custom_relids = fc_offset_relid_set(fc_cscan->custom_relids, fc_rtoffset);
}

/*
 * set_append_references
 *		对 Append 进行 set_plan_references 处理
 *
 * 我们尝试完全剥离 Append；如果不能，我们必须对其进行正常处理。
 */
static Plan * set_append_references(PlannerInfo *fc_root,
					  Append *fc_aplan,
					  int fc_rtoffset)
{
	ListCell   *fc_l;

	/*
	 * Append，像 Sort 等，并不实际评估其目标列表或
	 * 检查 quals。如果它只有一个子计划，那么它根本没有做任何有用的事情，
	 * 我们可以将其剥离。
	 */
	Assert(fc_aplan->plan.qual == NIL);

	/* 首先，我们必须对孩子类进行递归 */
	foreach(fc_l, fc_aplan->appendplans)
	{
		lfirst(fc_l) = set_plan_refs(fc_root, (Plan *) lfirst(fc_l), fc_rtoffset);
	}

	/*
	 * 看看是否安全完全剥离 Append。为了安全，这里必须只有一个子计划，
	 * 而且该子计划的并行意识必须与 Append 的并行意识相匹配。
	 * 后者的原因是，如果 Append 是并行安全的而子计划不是，
	 * 则调用计划可能会多次执行非并行安全的子计划。
	 */
	if (list_length(fc_aplan->appendplans) == 1 &&
		((Plan *) linitial(fc_aplan->appendplans))->parallel_aware == fc_aplan->plan.parallel_aware)
		return fc_clean_up_removed_plan_level((Plan *) fc_aplan,
										   (Plan *) linitial(fc_aplan->appendplans));

	/*
	 * 否则，根据需要清理 Append。在对子类递归之后执行此操作是可以的，
	 * 因为 set_dummy_tlist_references 不会查看这些内容。
	 */
	set_dummy_tlist_references((Plan *) fc_aplan, fc_rtoffset);

	fc_aplan->apprelids = fc_offset_relid_set(fc_aplan->apprelids, fc_rtoffset);

	if (fc_aplan->part_prune_info)
	{
		foreach(fc_l, fc_aplan->part_prune_info->prune_infos)
		{
			List	   *fc_prune_infos = lfirst(fc_l);
			ListCell   *fc_l2;

			foreach(fc_l2, fc_prune_infos)
			{
				PartitionedRelPruneInfo *fc_pinfo = lfirst(fc_l2);

				fc_pinfo->rtindex += fc_rtoffset;
				fc_pinfo->initial_pruning_steps =
					fix_scan_list(fc_root, fc_pinfo->initial_pruning_steps,
								  fc_rtoffset, 1);
				fc_pinfo->exec_pruning_steps =
					fix_scan_list(fc_root, fc_pinfo->exec_pruning_steps,
								  fc_rtoffset, 1);
			}
		}
	}

	/* 我们不需要递归到 lefttree 或 righttree ... */
	Assert(fc_aplan->plan.lefttree == NULL);
	Assert(fc_aplan->plan.righttree == NULL);

	return (Plan *) fc_aplan;
}

/*
 * set_mergeappend_references
 *		对 MergeAppend 进行 set_plan_references 处理
 *
 * 我们尝试完全剥离 MergeAppend；如果不能，我们必须对其进行正常处理。
 */
static Plan * set_mergeappend_references(PlannerInfo *fc_root,
						   MergeAppend *fc_mplan,
						   int fc_rtoffset)
{
	ListCell   *fc_l;

	/*
	 * MergeAppend，像 Sort 等，并不实际评估其目标列表
	 * 或检查 quals。如果它只有一个子计划，那么它根本没有做任何有用的事情，
	 * 我们可以将其剥离。
	 */
	Assert(fc_mplan->plan.qual == NIL);

	/* 首先，我们必须对孩子类进行递归 */
	foreach(fc_l, fc_mplan->mergeplans)
	{
		lfirst(fc_l) = set_plan_refs(fc_root, (Plan *) lfirst(fc_l), fc_rtoffset);
	}

	/*
	 * 看看是否安全完全剥离 MergeAppend。为了安全，这里必须只有一个子计划，
	 * 而且该子计划的并行意识必须与 MergeAppend 的并行意识相匹配。
	 * 后者的原因是，如果 MergeAppend 是并行安全的而子计划不是，
	 * 则调用计划可能会多次执行非并行安全的子计划。
	 */
	if (list_length(fc_mplan->mergeplans) == 1 &&
		((Plan *) linitial(fc_mplan->mergeplans))->parallel_aware == fc_mplan->plan.parallel_aware)
		return fc_clean_up_removed_plan_level((Plan *) fc_mplan,
										   (Plan *) linitial(fc_mplan->mergeplans));

	/*
	 * 否则，根据需要清理 MergeAppend。在对子类递归之后执行此操作是可以的，
	 * 因为 set_dummy_tlist_references 不会查看这些内容。
	 */
	set_dummy_tlist_references((Plan *) fc_mplan, fc_rtoffset);

	fc_mplan->apprelids = fc_offset_relid_set(fc_mplan->apprelids, fc_rtoffset);

	if (fc_mplan->part_prune_info)
	{
		foreach(fc_l, fc_mplan->part_prune_info->prune_infos)
		{
			List	   *fc_prune_infos = lfirst(fc_l);
			ListCell   *fc_l2;

			foreach(fc_l2, fc_prune_infos)
			{
				PartitionedRelPruneInfo *fc_pinfo = lfirst(fc_l2);

				fc_pinfo->rtindex += fc_rtoffset;
				fc_pinfo->initial_pruning_steps =
					fix_scan_list(fc_root, fc_pinfo->initial_pruning_steps,
								  fc_rtoffset, 1);
				fc_pinfo->exec_pruning_steps =
					fix_scan_list(fc_root, fc_pinfo->exec_pruning_steps,
								  fc_rtoffset, 1);
			}
		}
	}

	/* 我们不需要递归到 lefttree 或 righttree ... */
	Assert(fc_mplan->plan.lefttree == NULL);
	Assert(fc_mplan->plan.righttree == NULL);

	return (Plan *) fc_mplan;
}

/*
 * set_hash_references
 *	   对 Hash 节点进行 set_plan_references 处理
 */
static void set_hash_references(PlannerInfo *fc_root, Plan *fc_plan, int fc_rtoffset)
{
	Hash	   *fc_hplan = (Hash *) fc_plan;
	Plan	   *fc_outer_plan = fc_plan->lefttree;
	indexed_tlist *fc_outer_itlist;

	/*
	 * Hash 的 hashkeys 在将元组输入哈希表时使用，
	 * 因此让它们引用 Hash 的外部计划（该外部计划本身就是 HashJoin 的内部计划）。
	 */
	fc_outer_itlist = fc_build_tlist_index(fc_outer_plan->targetlist);
	fc_hplan->hashkeys = (List *)
		fc_fix_upper_expr(fc_root,
					   (Node *) fc_hplan->hashkeys,
					   fc_outer_itlist,
					   OUTER_VAR,
					   fc_rtoffset,
					   NUM_EXEC_QUAL(fc_plan));

	/* Hash 不进行投影 */
	set_dummy_tlist_references(fc_plan, fc_rtoffset);

	/* Hash 节点没有自己的 quals */
	Assert(fc_plan->qual == NIL);
}

/*
 * offset_relid_set
 *		对 Relids 集的成员应用 rtoffset。
 */
static Relids fc_offset_relid_set(Relids fc_relids, int fc_rtoffset)
{
	Relids		fc_result = NULL;
	int			fc_rtindex;

	/* 如果没有偏移量需要应用，我们不需要重新计算值 */
	if (fc_rtoffset == 0)
		return fc_relids;
	fc_rtindex = -1;
	while ((fc_rtindex = bms_next_member(fc_relids, fc_rtindex)) >= 0)
		fc_result = bms_add_member(fc_result, fc_rtindex + fc_rtoffset);
	return fc_result;
}

/*
 * copyVar
 *		复制一个 Var 节点。
 *
 * fix_scan_expr 和相关函数调用此操作的次数很多，因此
 * 采用定制的例程比使用通用的 copyObject() 函数更值得。
 */
static inline Var *
fc_copyVar(Var *fc_var)
{
	Var		   *fc_newvar = (Var *) palloc(sizeof(Var));

	*fc_newvar = *fc_var;
	return fc_newvar;
}

/*
 * fix_expr_common
 *		对表达式节点执行通用的 set_plan_references 处理
 *
 * 这是所有表达式修正变体共有的代码。
 * 我们必须查找 OpExpr 和相关节点的操作符操作码信息，
 * 将来自 regclass Const 节点的 OID 添加到 root->glob->relationOids 中，并
 * 将用户定义函数的 PlanInvalItems 添加到 root->glob->invalItems 中。
 * 我们还填写 GROUPING() 表达式的列索引列表。
 *
 * 我们假设直接更新操作码信息是可以的。因此，这可能会
 * 写入规划器的输入数据结构，但这是可以的。
 */
static void fc_fix_expr_common(PlannerInfo *fc_root, Node *fc_node)
{
	/* 我们假设调用者不会在空指针上调用我们 */
	if (IsA(fc_node, Aggref))
	{
		record_plan_function_dependency(fc_root,
										((Aggref *) fc_node)->aggfnoid);
	}
	else if (IsA(fc_node, WindowFunc))
	{
		record_plan_function_dependency(fc_root,
										((WindowFunc *) fc_node)->winfnoid);
	}
	else if (IsA(fc_node, FuncExpr))
	{
		record_plan_function_dependency(fc_root,
										((FuncExpr *) fc_node)->funcid);
	}
	else if (IsA(fc_node, OpExpr))
	{
		set_opfuncid((OpExpr *) fc_node);
		record_plan_function_dependency(fc_root,
										((OpExpr *) fc_node)->opfuncid);
	}
	else if (IsA(fc_node, DistinctExpr))
	{
		set_opfuncid((OpExpr *) fc_node);	/* 依赖于结构等价性 */
		record_plan_function_dependency(fc_root,
										((DistinctExpr *) fc_node)->opfuncid);
	}
	else if (IsA(fc_node, NullIfExpr))
	{
		set_opfuncid((OpExpr *) fc_node);	/* 依赖于结构等价性 */
		record_plan_function_dependency(fc_root,
										((NullIfExpr *) fc_node)->opfuncid);
	}
	else if (IsA(fc_node, ScalarArrayOpExpr))
	{
		ScalarArrayOpExpr *fc_saop = (ScalarArrayOpExpr *) fc_node;

		set_sa_opfuncid(fc_saop);
		record_plan_function_dependency(fc_root, fc_saop->opfuncid);

		if (OidIsValid(fc_saop->hashfuncid))
			record_plan_function_dependency(fc_root, fc_saop->hashfuncid);

		if (OidIsValid(fc_saop->negfuncid))
			record_plan_function_dependency(fc_root, fc_saop->negfuncid);
	}
	else if (IsA(fc_node, Const))
	{
		Const	   *fc_con = (Const *) fc_node;

		/* 检查 regclass 引用 */
		if (ISREGCLASSCONST(fc_con))
			fc_root->glob->relationOids =
				lappend_oid(fc_root->glob->relationOids,
							DatumGetObjectId(fc_con->constvalue));
	}
	else if (IsA(fc_node, GroupingFunc))
	{
		GroupingFunc *fc_g = (GroupingFunc *) fc_node;
		AttrNumber *fc_grouping_map = fc_root->grouping_map;

		/* 如果没有分组集，我们就不需要这个。 */

		Assert(fc_grouping_map || fc_g->cols == NIL);

		if (fc_grouping_map)
		{
			ListCell   *fc_lc;
			List	   *fc_cols = NIL;

			foreach(fc_lc, fc_g->refs)
			{
				fc_cols = lappend_int(fc_cols, fc_grouping_map[lfirst_int(fc_lc)]);
			}

			Assert(!fc_g->cols || equal(fc_cols, fc_g->cols));

			if (!fc_g->cols)
				fc_g->cols = fc_cols;
		}
	}
}

/*
 * fix_param_node
 *		对 Param 执行 set_plan_references 处理
 *
 * 如果它是一个 PARAM_MULTIEXPR，用 root->multiexpr_params 中的相应 Param 替换它；
 * 否则不需要任何更改。
 * 为了安全起见，无论如何我们都复制节点。
 */
static Node * fc_fix_param_node(PlannerInfo *fc_root, Param *fc_p)
{
	if (fc_p->paramkind == PARAM_MULTIEXPR)
	{
		int			fc_subqueryid = fc_p->paramid >> 16;
		int			fc_colno = fc_p->paramid & 0xFFFF;
		List	   *fc_params;

		if (fc_subqueryid <= 0 ||
			fc_subqueryid > list_length(fc_root->multiexpr_params))
			elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", fc_p->paramid);
		fc_params = (List *) list_nth(fc_root->multiexpr_params, fc_subqueryid - 1);
		if (fc_colno <= 0 || fc_colno > list_length(fc_params))
			elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", fc_p->paramid);
		return copyObject(list_nth(fc_params, fc_colno - 1));
	}
	return (Node *) copyObject(fc_p);
}

/*
 * fix_alternative_subplan
 *		对 AlternativeSubPlan 执行 set_plan_references 处理
 *
 * 选择一种替代实现并仅返回该实现，丢弃其余的 AlternativeSubPlan 结构。
 * 注意：调用者仍然必须递归进入结果！
 *
 * 我们不尝试修复父计划节点或更高级节点中的成本估算。
 */
static Node * fc_fix_alternative_subplan(PlannerInfo *fc_root, AlternativeSubPlan *fc_asplan,
						double fc_num_exec)
{
	SubPlan    *fc_bestplan = NULL;
	Cost		fc_bestcost = 0;
	ListCell   *fc_lc;

	/*
	 * 假设 num_exec 次执行，计算每个子计划的估计成本，
	 * 并保留最便宜的一个。如果估计值完全相等，
	 * 我们更倾向于选择较晚的计划；这有点任意，但在
	 * 当前用法中它使我们更倾向于打破与快速启动子计划的平局。
	 */
	Assert(fc_asplan->subplans != NIL);

	foreach(fc_lc, fc_asplan->subplans)
	{
		SubPlan    *fc_curplan = (SubPlan *) lfirst(fc_lc);
		Cost		fc_curcost;

		fc_curcost = fc_curplan->startup_cost + fc_num_exec * fc_curplan->per_call_cost;
		if (fc_bestplan == NULL || fc_curcost <= fc_bestcost)
		{
			fc_bestplan = fc_curplan;
			fc_bestcost = fc_curcost;
		}

		/* 还标记所有在 AlternativeSubPlans 中的子计划 */
		fc_root->isAltSubplan[fc_curplan->plan_id - 1] = true;
	}

	/* 标记我们选择的子计划 */
	fc_root->isUsedSubplan[fc_bestplan->plan_id - 1] = true;

	return (Node *) fc_bestplan;
}

/*
 * fix_scan_expr
 *		对扫描层表达式执行 set_plan_references 处理
 *
 * 这包括将所有 Vars 的 varnos 增加 rtoffset，
 * 替换 PARAM_MULTIEXPR Params，展开 PlaceHolderVars，
 * 替换应该由 initplan 输出 Params 替换的 Aggref 节点，
 * 为 AlternativeSubPlans 选择最佳实现，
 * 查找 OpExpr 和相关节点的操作符操作码信息，
 * 并将来自 regclass Const 节点的 OIDs 添加到 root->glob->relationOids 中。
 *
 * 'node'：要修改的表达式
 * 'rtoffset'：varnos 增加的量
 * 'num_exec'：表达式的估计执行次数
 *
 * 表达式树可以是复制并修改的，如果看起来安全，也可以原地修改。
 */
static Node * fc_fix_scan_expr(PlannerInfo *fc_root, Node *fc_node, int fc_rtoffset, double fc_num_exec)
{
	fix_scan_expr_context fc_context;

	fc_context.root = fc_root;
	fc_context.rtoffset = fc_rtoffset;
	fc_context.num_exec = fc_num_exec;

	if (fc_rtoffset != 0 ||
		fc_root->multiexpr_params != NIL ||
		fc_root->glob->lastPHId != 0 ||
		fc_root->minmax_aggs != NIL ||
		fc_root->hasAlternativeSubPlans)
	{
		return fc_fix_scan_expr_mutator(fc_node, &fc_context);
	}
	else
	{
		/*
		 * 如果 rtoffset == 0，我们不需要更改任何 Vars，并且如果
		 * 没有 MULTIEXPR 子查询，我们则不需要替换 PARAM_MULTIEXPR Params，
		 * 如果任何地方都没有占位符，我们也无需删除它们，
		 * 如果没有 minmax Aggrefs，我们也无需替换它们，
		 * 如果没有 AlternativeSubPlans，我们更无须删除它们。
		 * 那么，仅对输入节点树进行修改（因为唯一的更改是填充任何未设置的 opfuncid 字段，
		 * 这是无害的）是可以的。这节省了足够的周期，使其在微不足道的查询中变得显著。
		 */
		(void) fc_fix_scan_expr_walker(fc_node, &fc_context);
		return fc_node;
	}
}

static Node * fc_fix_scan_expr_mutator(Node *fc_node, fix_scan_expr_context *fc_context)
{
	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Var))
	{
		Var		   *fc_var = fc_copyVar((Var *) fc_node);

		Assert(fc_var->varlevelsup == 0);

		/*
		 * 我们不应该看到标记为 INNER_VAR、OUTER_VAR 或 ROWID_VAR 的 Vars。
		 * 但一个 indexqual 表达式可能包含 INDEX_VAR Vars。
		 */
		Assert(fc_var->varno != INNER_VAR);
		Assert(fc_var->varno != OUTER_VAR);
		Assert(fc_var->varno != ROWID_VAR);
		if (!IS_SPECIAL_VARNO(fc_var->varno))
			fc_var->varno += fc_context->rtoffset;
		if (fc_var->varnosyn > 0)
			fc_var->varnosyn += fc_context->rtoffset;
		return (Node *) fc_var;
	}
	if (IsA(fc_node, Param))
		return fc_fix_param_node(fc_context->root, (Param *) fc_node);
	if (IsA(fc_node, Aggref))
	{
		Aggref	   *fc_aggref = (Aggref *) fc_node;
		Param	   *fc_aggparam;

		/* 查看 Aggref 是否应该被替换为 Param */
		fc_aggparam = find_minmax_agg_replacement_param(fc_context->root, fc_aggref);
		if (fc_aggparam != NULL)
		{
			/* 为了安全起见，复制 Param */
			return (Node *) copyObject(fc_aggparam);
		}
		/* 如果没有匹配项，正常处理 */
	}
	if (IsA(fc_node, CurrentOfExpr))
	{
		CurrentOfExpr *fc_cexpr = (CurrentOfExpr *) copyObject(fc_node);

		Assert(!IS_SPECIAL_VARNO(fc_cexpr->cvarno));
		fc_cexpr->cvarno += fc_context->rtoffset;
		return (Node *) fc_cexpr;
	}
	if (IsA(fc_node, PlaceHolderVar))
	{
		/* 在扫描层上，我们应该总是评估包含的表达式 */
		PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;

		return fc_fix_scan_expr_mutator((Node *) fc_phv->phexpr, fc_context);
	}
	if (IsA(fc_node, AlternativeSubPlan))
		return fc_fix_scan_expr_mutator(fc_fix_alternative_subplan(fc_context->root,
															 (AlternativeSubPlan *) fc_node,
															 fc_context->num_exec),
									 fc_context);
	fc_fix_expr_common(fc_context->root, fc_node);
	return expression_tree_mutator(fc_node, fc_fix_scan_expr_mutator,
								   (void *) fc_context);
}

static bool fc_fix_scan_expr_walker(Node *fc_node, fix_scan_expr_context *fc_context)
{
	if (fc_node == NULL)
		return false;
	Assert(!(IsA(fc_node, Var) && ((Var *) fc_node)->varno == ROWID_VAR));
	Assert(!IsA(fc_node, PlaceHolderVar));
	Assert(!IsA(fc_node, AlternativeSubPlan));
	fc_fix_expr_common(fc_context->root, fc_node);
	return expression_tree_walker(fc_node, fc_fix_scan_expr_walker,
								  (void *) fc_context);
}

/*
 * set_join_references
 *	  修改连接节点的目标列表和限定条件，以引用其
 *	  子计划，通过将 varnos 设置为 OUTER_VAR 或 INNER_VAR 并将
 *	  attno 值设置为相应外部或内部连接元组项的结果域编号。
 *	  还对这些表达式执行操作码查找，并将 regclass OIDs 添加到 root->glob->relationOids 中。
 */
static void set_join_references(PlannerInfo *fc_root, Join *fc_join, int fc_rtoffset)
{
	Plan	   *fc_outer_plan = fc_join->plan.lefttree;
	Plan	   *fc_inner_plan = fc_join->plan.righttree;
	indexed_tlist *fc_outer_itlist;
	indexed_tlist *fc_inner_itlist;

	fc_outer_itlist = fc_build_tlist_index(fc_outer_plan->targetlist);
	fc_inner_itlist = fc_build_tlist_index(fc_inner_plan->targetlist);

	/*
	 * 首先处理 joinquals（包括合并或哈希子句）。这些
	 * 在逻辑上位于连接之下，因此它们始终可以使用
	 * 输入 tlists 中的所有值。现在处理 NestLoopParams 也是可以的，
	 * 因为那些无法引用可空子表达式。
	 */
	fc_join->joinqual = fc_fix_join_expr(fc_root,
								   fc_join->joinqual,
								   fc_outer_itlist,
								   fc_inner_itlist,
								   (Index) 0,
								   fc_rtoffset,
								   NUM_EXEC_QUAL((Plan *) fc_join));

	/* 现在处理连接类型特定的内容 */
	if (IsA(fc_join, NestLoop))
	{
		NestLoop   *fc_nl = (NestLoop *) fc_join;
		ListCell   *fc_lc;

		foreach(fc_lc, fc_nl->nestParams)
		{
			NestLoopParam *fc_nlp = (NestLoopParam *) lfirst(fc_lc);

			fc_nlp->paramval = (Var *) fc_fix_upper_expr(fc_root,
												   (Node *) fc_nlp->paramval,
												   fc_outer_itlist,
												   OUTER_VAR,
												   fc_rtoffset,
												   NUM_EXEC_TLIST(fc_outer_plan));
			/* 检查我们是否用简单的 Var 替换了任何 PlaceHolderVar */
			if (!(IsA(fc_nlp->paramval, Var) &&
				  fc_nlp->paramval->varno == OUTER_VAR))
				elog(ERROR, "NestLoopParam was not reduced to a simple Var");
		}
	}
	else if (IsA(fc_join, MergeJoin))
	{
		MergeJoin  *fc_mj = (MergeJoin *) fc_join;

		fc_mj->mergeclauses = fc_fix_join_expr(fc_root,
										 fc_mj->mergeclauses,
										 fc_outer_itlist,
										 fc_inner_itlist,
										 (Index) 0,
										 fc_rtoffset,
										 NUM_EXEC_QUAL((Plan *) fc_join));
	}
	else if (IsA(fc_join, HashJoin))
	{
		HashJoin   *fc_hj = (HashJoin *) fc_join;

		fc_hj->hashclauses = fc_fix_join_expr(fc_root,
										fc_hj->hashclauses,
										fc_outer_itlist,
										fc_inner_itlist,
										(Index) 0,
										fc_rtoffset,
										NUM_EXEC_QUAL((Plan *) fc_join));

		/*
		 * HashJoin 的 hashkeys 用于查找来自其
		 * 外部计划（而不是 Hash 节点！）中在哈希表中的匹配元组。
		 */
		fc_hj->hashkeys = (List *) fc_fix_upper_expr(fc_root,
											   (Node *) fc_hj->hashkeys,
											   fc_outer_itlist,
											   OUTER_VAR,
											   fc_rtoffset,
											   NUM_EXEC_QUAL((Plan *) fc_join));
	}

	
/*
	 * 现在我们需要修正目标列表和 qpqual，这些在逻辑上
	 * 位于连接之上。这意味着它们不应该重用任何在外连接的
	 * 可空一侧计算的输入表达式。Vars 和 PlaceHolderVars 是
	 * 可以的，因此我们可以通过清除 indexed_tlist 结构中的
	 * has_non_vars 来实现这一限制。
	 *
	 * XXX 这是一个糟糕的变通方法，因为我们没有清楚地区分
	 * 在外连接下出现的 Var 和在其上出现的“相同”Var。如果
	 * 我们做到了，我们就不需要以这种方式破解匹配规则。
	 */
	switch (fc_join->jointype)
	{
		case JOIN_LEFT:
		case JOIN_SEMI:
		case JOIN_ANTI:
			fc_inner_itlist->has_non_vars = false;
			break;
		case JOIN_RIGHT:
			fc_outer_itlist->has_non_vars = false;
			break;
		case JOIN_FULL:
			fc_outer_itlist->has_non_vars = false;
			fc_inner_itlist->has_non_vars = false;
			break;
		default:
			break;
	}

	fc_join->plan.targetlist = fc_fix_join_expr(fc_root,
										  fc_join->plan.targetlist,
										  fc_outer_itlist,
										  fc_inner_itlist,
										  (Index) 0,
										  fc_rtoffset,
										  NUM_EXEC_TLIST((Plan *) fc_join));
	fc_join->plan.qual = fc_fix_join_expr(fc_root,
									fc_join->plan.qual,
									fc_outer_itlist,
									fc_inner_itlist,
									(Index) 0,
									fc_rtoffset,
									NUM_EXEC_QUAL((Plan *) fc_join));

	pfree(fc_outer_itlist);
	pfree(fc_inner_itlist);
}

/*
 * set_upper_references
 *	  更新上层计划节点的目标列表和资格，以引用其
 *	  lefttree 子计划返回的元组。
 *	  还要为这些表达式执行操作码查找，并将 regclass OIDs
 *	  添加到 root->glob->relationOids。
 *
 * 这用于单输入计划类型，如 Agg、Group、Result。
 *
 * 在大多数情况下，我们必须将 tlist 和 qual 表达式中的个别
 * Vars 与子计划的 tlist 元素匹配（该元素是通过扁平化
 * 这些相同的表达式生成的，因此它应该具有所有所需的
 * 变量）。然而，有一个重要的例外：根据我们在计划树中的
 * 位置，排序/分组列可能已经被推入了未扁平化的子计划 tlist。
 * 如果这些值在输出中也需要，那么我们希望引用子计划 tlist
 * 元素，而不是重新计算表达式。
 */
static void set_upper_references(PlannerInfo *fc_root, Plan *fc_plan, int fc_rtoffset)
{
	Plan	   *fc_subplan = fc_plan->lefttree;
	indexed_tlist *fc_subplan_itlist;
	List	   *fc_output_targetlist;
	ListCell   *fc_l;

	fc_subplan_itlist = fc_build_tlist_index(fc_subplan->targetlist);

	fc_output_targetlist = NIL;
	foreach(fc_l, fc_plan->targetlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);
		Node	   *fc_newexpr;

		/* 如果它是一个排序/分组项目，首先尝试通过 sortref 匹配 */
		if (fc_tle->ressortgroupref != 0)
		{
			fc_newexpr = (Node *)
				fc_search_indexed_tlist_for_sortgroupref(fc_tle->expr,
													  fc_tle->ressortgroupref,
													  fc_subplan_itlist,
													  OUTER_VAR);
			if (!fc_newexpr)
				fc_newexpr = fc_fix_upper_expr(fc_root,
										 (Node *) fc_tle->expr,
										 fc_subplan_itlist,
										 OUTER_VAR,
										 fc_rtoffset,
										 NUM_EXEC_TLIST(fc_plan));
		}
		else
			fc_newexpr = fc_fix_upper_expr(fc_root,
									 (Node *) fc_tle->expr,
									 fc_subplan_itlist,
									 OUTER_VAR,
									 fc_rtoffset,
									 NUM_EXEC_TLIST(fc_plan));
		fc_tle = flatCopyTargetEntry(fc_tle);
		fc_tle->expr = (Expr *) fc_newexpr;
		fc_output_targetlist = lappend(fc_output_targetlist, fc_tle);
	}
	fc_plan->targetlist = fc_output_targetlist;

	fc_plan->qual = (List *)
		fc_fix_upper_expr(fc_root,
					   (Node *) fc_plan->qual,
					   fc_subplan_itlist,
					   OUTER_VAR,
					   fc_rtoffset,
					   NUM_EXEC_QUAL(fc_plan));

	pfree(fc_subplan_itlist);
}

/*
 * set_param_references
 *	  初始化 Gather 或 Gather merge 节点中的 initParam 列表，以便
 *	  包含所有需要在节点执行之前评估的参数的引用。它包含
 *	  被传递到其下方计划节点的 initplan 参数。
 */
static void set_param_references(PlannerInfo *fc_root, Plan *fc_plan)
{
	Assert(IsA(fc_plan, Gather) || IsA(fc_plan, GatherMerge));

	if (fc_plan->lefttree->extParam)
	{
		PlannerInfo *fc_proot;
		Bitmapset  *fc_initSetParam = NULL;
		ListCell   *fc_l;

		for (fc_proot = fc_root; fc_proot != NULL; fc_proot = fc_proot->parent_root)
		{
			foreach(fc_l, fc_proot->init_plans)
			{
				SubPlan    *fc_initsubplan = (SubPlan *) lfirst(fc_l);
				ListCell   *fc_l2;

				foreach(fc_l2, fc_initsubplan->setParam)
				{
					fc_initSetParam = bms_add_member(fc_initSetParam, lfirst_int(fc_l2));
				}
			}
		}

		/*
		 * 记住 Gather 或 Gather merge 节点的子节点使用的所有外部
		 * initplan 参数的列表。
		 */
		if (IsA(fc_plan, Gather))
			((Gather *) fc_plan)->initParam =
				bms_intersect(fc_plan->lefttree->extParam, fc_initSetParam);
		else
			((GatherMerge *) fc_plan)->initParam =
				bms_intersect(fc_plan->lefttree->extParam, fc_initSetParam);
	}
}

/*
 * 递归扫描表达式树并将 Aggrefs 转换为适合合并聚合的中间形式。这意味着 (1) 用单个参数替换每个聚合的参数列表，该参数是修改后的原始 Aggref，以显示部分聚合，(2) 更改上层 Aggref 以显示合并聚合。
 *
 * 在这一步之后，set_upper_references 将用引用下层 Agg 计划节点输出的 Vars 替换部分 Aggrefs，以便执行器看到的最终形式是带有 Var 作为输入的合并 Aggref。
 *
 * 将这一步推迟到 setrefs.c 的做法相当麻烦；理想情况下应在 createplan.c 中完成。困难在于，一旦我们修改了 Aggref 表达式，它们将不再与原始形式相等，因此跨计划节点级别的匹配将失败。因此，这必须在 Agg 上方的计划节点解析其子计划引用后进行。
 */
static Node * fc_convert_combining_aggrefs(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Aggref))
	{
		Aggref	   *fc_orig_agg = (Aggref *) fc_node;
		Aggref	   *fc_child_agg;
		Aggref	   *fc_parent_agg;

		/* 确定我们没有选择对任何不支持的情况进行部分处理 */
		Assert(fc_orig_agg->aggorder == NIL);
		Assert(fc_orig_agg->aggdistinct == NIL);

		/*
		 * 由于聚合调用不能嵌套，因此我们无需递归到参数中。但出于安全考虑，平面复制 Aggref 节点本身，而不是就地修改它。
		 */
		fc_child_agg = makeNode(Aggref);
		memcpy(fc_child_agg, fc_orig_agg, sizeof(Aggref));

		/*
		 * 对于父级 Aggref，我们想要复制原始聚合的所有字段 *除* 参数列表，我们将在下面替换，以及 aggfilter 表达式，后者只应由子级而不是父级应用。与其在这里明确了解所有其他字段，我们可以暂时修改 child_agg 以提供适合 copyObject 的源。
		 */
		fc_child_agg->args = NIL;
		fc_child_agg->aggfilter = NULL;
		fc_parent_agg = copyObject(fc_child_agg);
		fc_child_agg->args = fc_orig_agg->args;
		fc_child_agg->aggfilter = fc_orig_agg->aggfilter;

		/*
		 * 现在，设置 child_agg 以表示部分聚合的第一阶段。目前，假设需要序列化。
		 */
		mark_partial_aggref(fc_child_agg, AGGSPLIT_INITIAL_SERIAL);

		/*
		 * 并设置 parent_agg 以表示第二阶段。
		 */
		fc_parent_agg->args = list_make1(makeTargetEntry((Expr *) fc_child_agg,
													  1, NULL, false));
		mark_partial_aggref(fc_parent_agg, AGGSPLIT_FINAL_DESERIAL);

		return (Node *) fc_parent_agg;
	}
	return expression_tree_mutator(fc_node, fc_convert_combining_aggrefs,
								   (void *) fc_context);
}

/*
 * set_dummy_tlist_references
 *	  用一个简单的 OUTER_VAR 引用其子节点的列表替换上层计划节点的目标列表。
 *
 * 这用于像 Sort 和 Append 这样不评估其目标列表的计划类型。尽管执行器根本不在乎 tlist 中有什么内容，EXPLAIN 需要它看起来真实。
 *
 * 注意：我们几乎可以在这里使用 set_upper_references()，但由于缺少 lefttree 子计划，它在 Append 中失败。单用途代码更快。
 */
static void set_dummy_tlist_references(Plan *fc_plan, int fc_rtoffset)
{
	List	   *fc_output_targetlist;
	ListCell   *fc_l;

	fc_output_targetlist = NIL;
	foreach(fc_l, fc_plan->targetlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);
		Var		   *fc_oldvar = (Var *) fc_tle->expr;
		Var		   *fc_newvar;

		/*
		 * 和 search_indexed_tlist_for_non_var() 中一样，我们更倾向于将 Consts 保持为 Consts，而不是引用 Consts 的 Vars。在这里，没有速度上的优势可言，但它使 EXPLAIN 输出看起来更整洁，并且再次避免混淆执行器。
		 */
		if (IsA(fc_oldvar, Const))
		{
			/* 只是重用现有的 TLE 节点 */
			fc_output_targetlist = lappend(fc_output_targetlist, fc_tle);
			continue;
		}

		fc_newvar = makeVar(OUTER_VAR,
						 fc_tle->resno,
						 exprType((Node *) fc_oldvar),
						 exprTypmod((Node *) fc_oldvar),
						 exprCollation((Node *) fc_oldvar),
						 0);
		if (IsA(fc_oldvar, Var) &&
			fc_oldvar->varnosyn > 0)
		{
			fc_newvar->varnosyn = fc_oldvar->varnosyn + fc_rtoffset;
			fc_newvar->varattnosyn = fc_oldvar->varattnosyn;
		}
		else
		{
			fc_newvar->varnosyn = 0;	/* 从来不是一个普通的 Var */
			fc_newvar->varattnosyn = 0;
		}

		fc_tle = flatCopyTargetEntry(fc_tle);
		fc_tle->expr = (Expr *) fc_newvar;
		fc_output_targetlist = lappend(fc_output_targetlist, fc_tle);
	}
	fc_plan->targetlist = fc_output_targetlist;

	/* 我们在这里不触及 plan->qual */
}


/*
 * build_tlist_index --- 为子目标列表构建索引数据结构
 *
 * 在大多数情况下，子计划目标列表将是只有 Vars 的“平面”目标列表，因此我们尝试通过提前提取 Vars 的信息来优化该情况。将父目标列表与子目标列表匹配仍然是 O(N^2) 操作，但至少常数因子比普通的 tlist_member() 搜索小得多。
 *
 * 此函数的结果是一个 indexed_tlist 结构，传递给 search_indexed_tlist_for_var() 或 search_indexed_tlist_for_non_var()。完成后，可以使用单个 pfree() 释放 indexed_tlist。
 */
static indexed_tlist *
fc_build_tlist_index(List *fc_tlist)
{
	indexed_tlist *fc_itlist;
	tlist_vinfo *fc_vinfo;
	ListCell   *fc_l;

	/* 创建具有足够插槽以容纳所有目标列表条目的数据结构 */
	fc_itlist = (indexed_tlist *)
		palloc(offsetof(indexed_tlist, vars) +
			   list_length(fc_tlist) * sizeof(tlist_vinfo));

	fc_itlist->tlist = fc_tlist;
	fc_itlist->has_ph_vars = false;
	fc_itlist->has_non_vars = false;

	/* 查找 Vars 并填充索引数组 */
	fc_vinfo = fc_itlist->vars;
	foreach(fc_l, fc_tlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);

		if (fc_tle->expr && IsA(fc_tle->expr, Var))
		{
			Var		   *fc_var = (Var *) fc_tle->expr;

			fc_vinfo->varno = fc_var->varno;
			fc_vinfo->varattno = fc_var->varattno;
			fc_vinfo->resno = fc_tle->resno;
			fc_vinfo++;
		}
		else if (fc_tle->expr && IsA(fc_tle->expr, PlaceHolderVar))
			fc_itlist->has_ph_vars = true;
		else
			fc_itlist->has_non_vars = true;
	}

	fc_itlist->num_vars = (fc_vinfo - fc_itlist->vars);

	return fc_itlist;
}

/*
 * build_tlist_index_other_vars --- 构建受限制的目标列表索引
 *
 * 这类似于 build_tlist_index，但我们仅对属于某个其他关系的 Vars 的目标列表条目进行索引。我们将设置 has_ph_vars（允许匹配 PlaceHolderVars），但不设置 has_non_vars（因此除了 Vars 和 PlaceHolderVars 之外没有其他东西可以匹配）。
 */
static indexed_tlist *
fc_build_tlist_index_other_vars(List *fc_tlist, int fc_ignore_rel)
{
	indexed_tlist *fc_itlist;
	tlist_vinfo *fc_vinfo;
	ListCell   *fc_l;

	/* 创建具有足够插槽以容纳所有目标列表条目的数据结构 */
	fc_itlist = (indexed_tlist *)
		palloc(offsetof(indexed_tlist, vars) +
			   list_length(fc_tlist) * sizeof(tlist_vinfo));

	fc_itlist->tlist = fc_tlist;
	fc_itlist->has_ph_vars = false;
	fc_itlist->has_non_vars = false;

	/* 查找所需的 Vars 并填充索引数组 */
	fc_vinfo = fc_itlist->vars;
	foreach(fc_l, fc_tlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);

		if (fc_tle->expr && IsA(fc_tle->expr, Var))
		{
			Var		   *fc_var = (Var *) fc_tle->expr;

			if (fc_var->varno != fc_ignore_rel)
			{
				fc_vinfo->varno = fc_var->varno;
				fc_vinfo->varattno = fc_var->varattno;
				fc_vinfo->resno = fc_tle->resno;
				fc_vinfo++;
			}
		}
		else if (fc_tle->expr && IsA(fc_tle->expr, PlaceHolderVar))
			fc_itlist->has_ph_vars = true;
	}

	fc_itlist->num_vars = (fc_vinfo - fc_itlist->vars);

	return fc_itlist;
}

/*
 * search_indexed_tlist_for_var --- 在索引目标列表中查找 Var
 *
 * 如果找到匹配项，返回给定 Var 的副本，适当修改 varno/varattno（即，newvarno 和 TLE 条目的 resno）。还确保 varnosyn 增加 rtoffset。
 * 如果没有匹配项，返回 NULL。
 */
static Var * fc_search_indexed_tlist_for_var(Var *fc_var, indexed_tlist *fc_itlist,
							 int fc_newvarno, int fc_rtoffset)
{
	int			fc_varno = fc_var->varno;
	AttrNumber	fc_varattno = fc_var->varattno;
	tlist_vinfo *fc_vinfo;
	int			fc_i;

	fc_vinfo = fc_itlist->vars;
	fc_i = fc_itlist->num_vars;
	while (fc_i-- > 0)
	{
		if (fc_vinfo->varno == fc_varno && fc_vinfo->varattno == fc_varattno)
		{
			/* 找到匹配项 */
			Var		   *fc_newvar = fc_copyVar(fc_var);

			fc_newvar->varno = fc_newvarno;
			fc_newvar->varattno = fc_vinfo->resno;
			if (fc_newvar->varnosyn > 0)
				fc_newvar->varnosyn += fc_rtoffset;
			return fc_newvar;
		}
		fc_vinfo++;
	}
	return NULL;				/* no match */
}

/*
 * search_indexed_tlist_for_non_var --- 在索引目标列表中查找非 Var
 *
 * 如果找到匹配项，返回构造的 Var，以引用目标列表项。如果没有匹配项，返回 NULL。
 *
 * 注意：除非 itlist->has_ph_vars 或 itlist->has_non_vars，否则调用此方法是浪费时间。此外，set_join_references() 依赖于通过清除 itlist->has_non_vars 来防止匹配非 Vars，因此在没有设置的情况下调用它会造成正确性问题。
 */
static Var * fc_search_indexed_tlist_for_non_var(Expr *fc_node,
								 indexed_tlist *fc_itlist, int fc_newvarno)
{
	TargetEntry *fc_tle;

	
/*
	 * 如果它是一个简单的 Const，将其替换为 Var 是愚蠢的，即使下面恰好有一个相同的 Const；Var 的执行开销比 Const 更大。更重要的是，替换它可能会混淆执行器中某些期望看到简单 Const 的地方，例如，删除的列。
	 */
	if (IsA(fc_node, Const))
		return NULL;

	fc_tle = tlist_member(fc_node, fc_itlist->tlist);
	if (fc_tle)
	{
		/* 找到匹配的子计划输出表达式 */
		Var		   *fc_newvar;

		fc_newvar = makeVarFromTargetEntry(fc_newvarno, fc_tle);
		fc_newvar->varnosyn = 0;	/* 从来不是一个普通的 Var */
		fc_newvar->varattnosyn = 0;
		return fc_newvar;
	}
	return NULL;				/* no match */
}

/*
 * search_indexed_tlist_for_sortgroupref --- 查找排序/分组表达式
 *
 * 如果找到匹配项，返回一个构造的 Var，引用目标列表项。
 * 如果没有匹配，返回 NULL。
 *
 * 这是为了确保我们在存在多个文本上相等但不稳定的排序表达式时，选择正确的子计划 TLE。而且它比 search_indexed_tlist_for_non_var 更快。
 */
static Var * fc_search_indexed_tlist_for_sortgroupref(Expr *fc_node,
									  Index fc_sortgroupref,
									  indexed_tlist *fc_itlist,
									  int fc_newvarno)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_itlist->tlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_lc);

		/* equal() 检查应该是多余的，但我们还是保持警惕 */
		if (fc_tle->ressortgroupref == fc_sortgroupref &&
			equal(fc_node, fc_tle->expr))
		{
			/* 找到匹配的子计划输出表达式 */
			Var		   *fc_newvar;

			fc_newvar = makeVarFromTargetEntry(fc_newvarno, fc_tle);
			fc_newvar->varnosyn = 0;	/* 从来不是一个普通的 Var */
			fc_newvar->varattnosyn = 0;
			return fc_newvar;
		}
	}
	return NULL;				/* no match */
}

/*
 * fix_join_expr
 *	   通过将子句中变量的 varno/varattno 值更改为引用外部和内部连接关系目标列表的目标列表项，创建一组新的目标列表条目或连接条件。
 *	   还执行操作码查找并将 regclass OIDs 添加到 root->glob->relationOids。
 *
 * 这在四种不同的场景中使用：
 * 1) 正常连接子句，其中子句中的所有 Vars *必须*被替换为 OUTER_VAR 或 INNER_VAR 引用。在这种情况下，acceptable_rel 应该为零，以便任何未能匹配 Var 的情况都会被报告为错误。
 * 2) RETURNING 子句，可能包含目标关系的 Vars 和其他关系的 Vars。在这种情况下，我们希望将其他关系的 Vars 替换为 OUTER_VAR 引用，而让目标 Vars 保持不变。因此 inner_itlist = NULL 和 acceptable_rel = 目标关系的 ID 应该被传递。
 * 3) ON CONFLICT UPDATE SET/WHERE 子句。在这里，引用 EXCLUDED 的部分将被替换为 INNER_VAR 引用，而让目标 Vars（待更新关系）保持不变。对应地，inner_itlist 应该是 EXCLUDED 元素，outer_itlist = NULL，acceptable_rel 为目标关系。
 * 4) MERGE。在这种情况下，引用源关系的部分应该被替换为 INNER_VAR 引用，而让目标关系的 Vars（待修改关系）保持不变。因此 inner_itlist 应该是源关系元素，outer_itlist = NULL，acceptable_rel 为目标关系。
 *
 * 'clauses' 是目标列表或连接子句列表
 * 'outer_itlist' 是外部连接关系的索引目标列表，或者为 NULL
 * 'inner_itlist' 是内部连接关系的索引目标列表，或者为 NULL
 * 'acceptable_rel' 是零或者一个关系的 rangetable 索引，其 Vars 可能在子句中出现而不会引发错误
 * 'rtoffset': varnos 应该增加多少
 * 'num_exec': 表达式的估计执行次数
 *
 * 返回新的表达式树。原始子句结构没有修改。
 */
static List * fc_fix_join_expr(PlannerInfo *fc_root,
			  List *fc_clauses,
			  indexed_tlist *fc_outer_itlist,
			  indexed_tlist *fc_inner_itlist,
			  Index fc_acceptable_rel,
			  int fc_rtoffset,
			  double fc_num_exec)
{
	fix_join_expr_context fc_context;

	fc_context.root = fc_root;
	fc_context.outer_itlist = fc_outer_itlist;
	fc_context.inner_itlist = fc_inner_itlist;
	fc_context.acceptable_rel = fc_acceptable_rel;
	fc_context.rtoffset = fc_rtoffset;
	fc_context.num_exec = fc_num_exec;
	return (List *) fc_fix_join_expr_mutator((Node *) fc_clauses, &fc_context);
}

static Node * fc_fix_join_expr_mutator(Node *fc_node, fix_join_expr_context *fc_context)
{
	Var		   *fc_newvar;

	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Var))
	{
		Var		   *fc_var = (Var *) fc_node;

		/* 首先在外部查找输入 tlists 中的 Var */
		if (fc_context->outer_itlist)
		{
			fc_newvar = fc_search_indexed_tlist_for_var(fc_var,
												  fc_context->outer_itlist,
												  OUTER_VAR,
												  fc_context->rtoffset);
			if (fc_newvar)
				return (Node *) fc_newvar;
		}

		/* 然后在内部。 */
		if (fc_context->inner_itlist)
		{
			fc_newvar = fc_search_indexed_tlist_for_var(fc_var,
												  fc_context->inner_itlist,
												  INNER_VAR,
												  fc_context->rtoffset);
			if (fc_newvar)
				return (Node *) fc_newvar;
		}

		/* 如果这是 acceptable_rel，进行调整并返回 */
		if (fc_var->varno == fc_context->acceptable_rel)
		{
			fc_var = fc_copyVar(fc_var);
			fc_var->varno += fc_context->rtoffset;
			if (fc_var->varnosyn > 0)
				fc_var->varnosyn += fc_context->rtoffset;
			return (Node *) fc_var;
		}

		/* 找不到 Var 的引用 */
		elog(ERROR, "variable not found in subplan target lists");
	}
	if (IsA(fc_node, PlaceHolderVar))
	{
		PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;

		/* 查看 PlaceHolderVar 是否从较低的计划节点冒泡上来 */
		if (fc_context->outer_itlist && fc_context->outer_itlist->has_ph_vars)
		{
			fc_newvar = fc_search_indexed_tlist_for_non_var((Expr *) fc_phv,
													  fc_context->outer_itlist,
													  OUTER_VAR);
			if (fc_newvar)
				return (Node *) fc_newvar;
		}
		if (fc_context->inner_itlist && fc_context->inner_itlist->has_ph_vars)
		{
			fc_newvar = fc_search_indexed_tlist_for_non_var((Expr *) fc_phv,
													  fc_context->inner_itlist,
													  INNER_VAR);
			if (fc_newvar)
				return (Node *) fc_newvar;
		}

		/* 如果输入计划没有提供，评估包含的表达式 */
		return fc_fix_join_expr_mutator((Node *) fc_phv->phexpr, fc_context);
	}
	/* 如果目标列表有任何内容，也尝试匹配更复杂的表达式 */
	if (fc_context->outer_itlist && fc_context->outer_itlist->has_non_vars)
	{
		fc_newvar = fc_search_indexed_tlist_for_non_var((Expr *) fc_node,
												  fc_context->outer_itlist,
												  OUTER_VAR);
		if (fc_newvar)
			return (Node *) fc_newvar;
	}
	if (fc_context->inner_itlist && fc_context->inner_itlist->has_non_vars)
	{
		fc_newvar = fc_search_indexed_tlist_for_non_var((Expr *) fc_node,
												  fc_context->inner_itlist,
												  INNER_VAR);
		if (fc_newvar)
			return (Node *) fc_newvar;
	}
	/* 特殊情况（仅在无法匹配到较低的目标列表后应用） */
	if (IsA(fc_node, Param))
		return fc_fix_param_node(fc_context->root, (Param *) fc_node);
	if (IsA(fc_node, AlternativeSubPlan))
		return fc_fix_join_expr_mutator(fc_fix_alternative_subplan(fc_context->root,
															 (AlternativeSubPlan *) fc_node,
															 fc_context->num_exec),
									 fc_context);
	fc_fix_expr_common(fc_context->root, fc_node);
	return expression_tree_mutator(fc_node,
								   fc_fix_join_expr_mutator,
								   (void *) fc_context);
}

/*
 * fix_upper_expr
 *		修改表达式树，使所有 Var 节点引用子计划的输出。
 *		还会查找应由 initplan 输出参数替换的 Aggref 节点。
 *		还执行操作码查找，并将 regclass OID 添加到 root->glob->relationOids 中。
 *
 * 这用于修复非连接上层计划节点的目标和条件表达式，以及仅索引扫描节点。
 *
 * 如果在子计划目标列表中找不到匹配的变量，将引发错误
 * --- 所以此例程仅应应用于子计划的目标列表是通过展平父节点中使用的表达式生成的节点。
 *
 * 如果 itlist->has_non_vars 为真，则我们尝试将整个子表达式
 * 与子计划目标列表的元素进行匹配，以便避免重新计算
 * 已经由子计划计算的表达式。（这相对昂贵，因此在
 * 子计划目标列表只是展平的 Var 列表的常见情况下，我们不想尝试它。）
 *
 * 'node'：要修复的树（目标项或条件）
 * 'subplan_itlist'：子计划的索引目标列表（或索引）
 * 'newvarno'：用于引用目标列表元素的 Var 的 varno
 * 'rtoffset'：varnos 增量的多少
 * 'num_exec'：表达式的估计执行次数
 *
 * 结果树是原始树的副本，其中所有 Var 节点的
 * varno = newvarno，varattno = 对应目标列表元素的 resno。
 * 原始树没有被修改。
 */
static Node * fc_fix_upper_expr(PlannerInfo *fc_root,
			   Node *fc_node,
			   indexed_tlist *fc_subplan_itlist,
			   int fc_newvarno,
			   int fc_rtoffset,
			   double fc_num_exec)
{
	fix_upper_expr_context fc_context;

	fc_context.root = fc_root;
	fc_context.subplan_itlist = fc_subplan_itlist;
	fc_context.newvarno = fc_newvarno;
	fc_context.rtoffset = fc_rtoffset;
	fc_context.num_exec = fc_num_exec;
	return fc_fix_upper_expr_mutator(fc_node, &fc_context);
}

static Node * fc_fix_upper_expr_mutator(Node *fc_node, fix_upper_expr_context *fc_context)
{
	Var		   *fc_newvar;

	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Var))
	{
		Var		   *fc_var = (Var *) fc_node;

		fc_newvar = fc_search_indexed_tlist_for_var(fc_var,
											  fc_context->subplan_itlist,
											  fc_context->newvarno,
											  fc_context->rtoffset);
		if (!fc_newvar)
			elog(ERROR, "variable not found in subplan target list");
		return (Node *) fc_newvar;
	}
	if (IsA(fc_node, PlaceHolderVar))
	{
		PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;

		/* 查看 PlaceHolderVar 是否从较低的计划节点冒泡上来 */
		if (fc_context->subplan_itlist->has_ph_vars)
		{
			fc_newvar = fc_search_indexed_tlist_for_non_var((Expr *) fc_phv,
													  fc_context->subplan_itlist,
													  fc_context->newvarno);
			if (fc_newvar)
				return (Node *) fc_newvar;
		}
		/* 如果输入计划没有提供，评估包含的表达式 */
		return fc_fix_upper_expr_mutator((Node *) fc_phv->phexpr, fc_context);
	}
	/* 如果目标列表有任何内容，也尝试匹配更复杂的表达式 */
	if (fc_context->subplan_itlist->has_non_vars)
	{
		fc_newvar = fc_search_indexed_tlist_for_non_var((Expr *) fc_node,
												  fc_context->subplan_itlist,
												  fc_context->newvarno);
		if (fc_newvar)
			return (Node *) fc_newvar;
	}
	/* 特殊情况（仅在无法匹配到较低的目标列表后应用） */
	if (IsA(fc_node, Param))
		return fc_fix_param_node(fc_context->root, (Param *) fc_node);
	if (IsA(fc_node, Aggref))
	{
		Aggref	   *fc_aggref = (Aggref *) fc_node;
		Param	   *fc_aggparam;

		/* 查看 Aggref 是否应该被替换为 Param */
		fc_aggparam = find_minmax_agg_replacement_param(fc_context->root, fc_aggref);
		if (fc_aggparam != NULL)
		{
			/* 为了安全起见，复制 Param */
			return (Node *) copyObject(fc_aggparam);
		}
		/* 如果没有匹配项，正常处理 */
	}
	if (IsA(fc_node, AlternativeSubPlan))
		return fc_fix_upper_expr_mutator(fc_fix_alternative_subplan(fc_context->root,
															  (AlternativeSubPlan *) fc_node,
															  fc_context->num_exec),
									  fc_context);
	fc_fix_expr_common(fc_context->root, fc_node);
	return expression_tree_mutator(fc_node,
								   fc_fix_upper_expr_mutator,
								   (void *) fc_context);
}

/*
 * set_returning_clause_references
 *		对 RETURNING 目标列表执行 setrefs.c 的工作
 *
 * 如果查询涉及的不仅仅是结果表，我们必须
 * 调整任何引用其他表的 Var，以引用顶层
 * 子计划的目标列表中的垃圾条目。然而，引用结果
 * 表的 Var 应保持不变（执行器将在触发器触发后使用实际堆元组进行评估，若有的话）。
 * 在调整后的 RETURNING 列表中，结果表的 Var 将保留其原始
 * varno（加上 rtoffset），但其他关系的 Var 将具有 OUTER_VAR 的 varno。
 *
 * 我们还必须执行操作码查找并将 regclass OID 添加到
 * root->glob->relationOids 中。
 *
 * 'rlist'：要修复的 RETURNING 目标列表
 * 'topplan'：将在 ModifyTable 节点下的顶层子计划节点（注意它尚未通过 set_plan_refs 处理）
 * 'resultRelation'：相关结果关系的 RT 索引
 * 'rtoffset'：varnos 增量的多少
 *
 * 注意：给定的 'root' 是父查询级别，而不是 'topplan'。
 * 目前这没有关系，因为我们仅访问 root->glob 中的依赖项
 * 列表，但如果我们想要一个实际与子计划匹配的根
 * 就需要一些破解。
 *
 * 注意：resultRelation 尚未通过 rtoffset 进行调整。
 */
static List * set_returning_clause_references(PlannerInfo *fc_root,
								List *fc_rlist,
								Plan *fc_topplan,
								Index fc_resultRelation,
								int fc_rtoffset)
{
	indexed_tlist *fc_itlist;

	/*
	 * 我们可以通过滥用曾经处理内部索引扫描修复的 fix_join_expr
	 * 机制来执行所需的 Var 修复。我们在顶层计划的目标列表中搜索非结果关系的 Var，并使用
	 * fix_join_expr 将 RETURNING Var 转换为对那些目标列表
	 * 条目的引用，同时保持结果关系的 Var 不变。
	 *
	 * 还会在目标列表中查找 PlaceHolderVar，但不会有
	 * 更复杂的表达式。请注意，PlaceHolderVar 不可能引用结果关系，
	 * 因为结果永远不会在外部连接下。如果可能发生这种情况，
	 * 我们必须准备拆解 PlaceHolderVar 并评估其包含的
	 * 表达式。
	 */
	fc_itlist = fc_build_tlist_index_other_vars(fc_topplan->targetlist, fc_resultRelation);

	fc_rlist = fc_fix_join_expr(fc_root,
						  fc_rlist,
						  fc_itlist,
						  NULL,
						  fc_resultRelation,
						  fc_rtoffset,
						  NUM_EXEC_TLIST(fc_topplan));

	pfree(fc_itlist);

	return fc_rlist;
}

/*
 * fix_windowagg_condition_expr_mutator
 *		用于用引用该 WindowFunc 的目标列表中的相应 Var 替换 WindowFuncs 的变异器函数。
 */
static Node * fc_fix_windowagg_condition_expr_mutator(Node *fc_node,
									 fix_windowagg_cond_context *fc_context)
{
	if (fc_node == NULL)
		return NULL;

	if (IsA(fc_node, WindowFunc))
	{
		Var		   *fc_newvar;

		fc_newvar = fc_search_indexed_tlist_for_non_var((Expr *) fc_node,
												  fc_context->subplan_itlist,
												  fc_context->newvarno);
		if (fc_newvar)
			return (Node *) fc_newvar;
		elog(ERROR, "WindowFunc not found in subplan target lists");
	}

	return expression_tree_mutator(fc_node,
								   fc_fix_windowagg_condition_expr_mutator,
								   (void *) fc_context);
}

/*
 * fix_windowagg_condition_expr
 *		转换 'runcondition' 中的引用，以便任何 WindowFunc
 *		引用都被替换为引用 'subplan_itlist' 中匹配的
 *		WindowFunc 的 Var。
 */
static List * fc_fix_windowagg_condition_expr(PlannerInfo *fc_root,
							 List *fc_runcondition,
							 indexed_tlist *fc_subplan_itlist)
{
	fix_windowagg_cond_context fc_context;

	fc_context.root = fc_root;
	fc_context.subplan_itlist = fc_subplan_itlist;
	fc_context.newvarno = 0;

	return (List *) fc_fix_windowagg_condition_expr_mutator((Node *) fc_runcondition,
														 &fc_context);
}

/*
 * set_windowagg_runcondition_references
 *		转换 'runcondition' 中的引用，以便任何 WindowFunc 引用都被替换为引用 'plan' 目标列表中匹配的 WindowFunc 的 Var。
 */
static List * set_windowagg_runcondition_references(PlannerInfo *fc_root,
									  List *fc_runcondition,
									  Plan *fc_plan)
{
	List	   *fc_newlist;
	indexed_tlist *fc_itlist;

	fc_itlist = fc_build_tlist_index(fc_plan->targetlist);

	fc_newlist = fc_fix_windowagg_condition_expr(fc_root, fc_runcondition, fc_itlist);

	pfree(fc_itlist);

	return fc_newlist;
}

/*
 * find_minmax_agg_replacement_param
 *		如果给定的 Aggref 是我们优化为子查询的那种（参见 planagg.c），则返回应替换它的 Param。否则返回 NULL。
 *
 * 这被导出，以便 SS_finalize_plan 可以在 setrefs.c 运行之前使用它。请注意，在我们从 MinMaxAggPath 构建 Plan 之前，它将不会找到任何内容，因为 root->minmax_aggs 否则不会被填充。
 */
Param * find_minmax_agg_replacement_param(PlannerInfo *fc_root, Aggref *fc_aggref)
{
	if (fc_root->minmax_aggs != NIL &&
		list_length(fc_aggref->args) == 1)
	{
		TargetEntry *fc_curTarget = (TargetEntry *) linitial(fc_aggref->args);
		ListCell   *fc_lc;

		foreach(fc_lc, fc_root->minmax_aggs)
		{
			MinMaxAggInfo *fc_mminfo = (MinMaxAggInfo *) lfirst(fc_lc);

			if (fc_mminfo->aggfnoid == fc_aggref->aggfnoid &&
				equal(fc_mminfo->target, fc_curTarget->expr))
				return fc_mminfo->param;
		}
	}
	return NULL;
}


/*****************************************************************************
 *					查询依赖管理
 *****************************************************************************/


/*
 * record_plan_function_dependency
 *		标记当前计划依赖于特定函数。
 *
 * 这被导出，以便函数内联代码可以记录一个
 * 对它从计划树中移除的函数的依赖。
 */
void record_plan_function_dependency(PlannerInfo *fc_root, Oid fc_funcid)
{
	/*
	 * 出于性能原因，我们不追踪内置函数；
	 * 我们只是假设它们永远不会改变（或者至少不会以
	 * 使使用它们的计划失效的方式改变）。为此，我们可以将
	 * 内置函数视为OID小于FirstUnpinnedObjectId的函数。
	 * 请注意，OID生成器保证在启动后永远不会生成这样的OID，
	 * 即使在OID回绕时也是如此。
	 */
	if (fc_funcid >= (Oid) FirstUnpinnedObjectId)
	{
		PlanInvalItem *fc_inval_item = makeNode(PlanInvalItem);

		/*
		 * 使用pg_proc上的任何系统缓存都可以，但最简单的是
		 * PROCOID，因为我们已经手头有函数的OID。请注意
		 * plancache.c知道我们使用PROCOID。
		 */
		fc_inval_item->cacheId = PROCOID;
		fc_inval_item->hashValue = GetSysCacheHashValue1(PROCOID,
													  ObjectIdGetDatum(fc_funcid));

		fc_root->glob->invalItems = lappend(fc_root->glob->invalItems, fc_inval_item);
	}
}

/*
 * record_plan_type_dependency
 *		标记当前计划依赖于特定类型。
 *
 * 这被导出，以便eval_const_expressions可以记录一个
 * 对它移除CoerceToDomain节点的域的依赖。
 *
 * 我们目前不需要记录计划中包含CoerceToDomain节点的
 * 域的依赖，尽管将来可能会改变。因此，这在此模块中
 * 实际上并未被调用，尽管将来fix_expr_common可能会调用它。
 */
void record_plan_type_dependency(PlannerInfo *fc_root, Oid fc_typid)
{
	/*
	 * 像record_plan_function_dependency中一样，忽略有人
	 * 会改变内置域的可能性。
	 */
	if (fc_typid >= (Oid) FirstUnpinnedObjectId)
	{
		PlanInvalItem *fc_inval_item = makeNode(PlanInvalItem);

		/*
		 * 使用pg_type上的任何系统缓存都可以，但最简单的是
		 * TYPEOID，因为我们已经手头有类型的OID。请注意
		 * plancache.c知道我们使用TYPEOID。
		 */
		fc_inval_item->cacheId = TYPEOID;
		fc_inval_item->hashValue = GetSysCacheHashValue1(TYPEOID,
													  ObjectIdGetDatum(fc_typid));

		fc_root->glob->invalItems = lappend(fc_root->glob->invalItems, fc_inval_item);
	}
}

/*
 * extract_query_dependencies
 *		给定一个重写但尚未规划的查询或查询列表
 *		（即Query节点或Query节点列表），提取依赖关系
 *		就像set_plan_references会做的那样。同时检测是否有
 *		重写步骤受到RLS的影响。
 *
 * 这对于plancache.c处理未计划查询的缓存失效是必要的。
 *
 * 注意：这不通过eval_const_expressions，因此不反映其
 * 对inlined函数和省略的CoerceToDomain节点的添加到
 * invalItems列表中的情况。显然，对于函数来说这是可以的，
 * 因为我们在原始查询树中会看到它们。对于域来说，
 * 这是可行的，因为我们不关心域，除非它们被省略。
 * 也就是说，一个计划可能有查询树没有的域依赖。
 */
void extract_query_dependencies(Node *fc_query,
						   List **fc_relationOids,
						   List **fc_invalItems,
						   bool *fc_hasRowSecurity)
{
	PlannerGlobal fc_glob;
	PlannerInfo fc_root;

	/* 制作虚拟的计划状态，以便我们可以使用该模块的机制 */
	MemSet(&fc_glob, 0, sizeof(fc_glob));
	fc_glob.type = T_PlannerGlobal;
	fc_glob.relationOids = NIL;
	fc_glob.invalItems = NIL;
	/* 黑客：我们使用glob.dependsOnRole收集hasRowSecurity标志 */
	fc_glob.dependsOnRole = false;

	MemSet(&fc_root, 0, sizeof(fc_root));
	fc_root.type = T_PlannerInfo;
	fc_root.glob = &fc_glob;

	(void) extract_query_dependencies_walker(fc_query, &fc_root);

	*fc_relationOids = fc_glob.relationOids;
	*fc_invalItems = fc_glob.invalItems;
	*fc_hasRowSecurity = fc_glob.dependsOnRole;
}

/*
 * extract_query_dependencies的树遍历器。
 *
 * 这被导出，以便expression_planner_with_deps可以在
 * 简单表达式上调用它（计划后，而不是计划前，在该情况下）。
 * 在这种用法中，glob.dependsOnRole并不重要，但relationOids
 * 和invalItems列表根据需要进行添加。
 */
bool extract_query_dependencies_walker(Node *fc_node, PlannerInfo *fc_context)
{
	if (fc_node == NULL)
		return false;
	Assert(!IsA(fc_node, PlaceHolderVar));
	if (IsA(fc_node, Query))
	{
		Query	   *fc_query = (Query *) fc_node;
		ListCell   *fc_lc;

		if (fc_query->commandType == CMD_UTILITY)
		{
			/*
			 * 该逻辑必须处理任何需要非平凡解析分析的
			 * 实用命令（参见stmt_requires_parse_analysis）。
			 *
			 * 特别是，CALL需要自己的处理。
			 */
			if (IsA(fc_query->utilityStmt, CallStmt))
			{
				CallStmt   *fc_callstmt = (CallStmt *) fc_query->utilityStmt;

				/* 我们不需要检查funccall，只需检查转换后的表达式 */
				(void) extract_query_dependencies_walker((Node *) fc_callstmt->funcexpr,
														 fc_context);
				(void) extract_query_dependencies_walker((Node *) fc_callstmt->outargs,
														 fc_context);
				return false;
			}

			/*
			 * 忽略其他实用语句，除了那些（例如EXPLAIN）
			 * 包含已解析但未规划的查询。对于这些，我们
			 * 只需将注意力转移到包含的查询上。
			 */
			fc_query = UtilityContainsQuery(fc_query->utilityStmt);
			if (fc_query == NULL)
				return false;
		}

		/* 记住是否有任何查询的RLS资格由重写器应用 */
		if (fc_query->hasRowSecurity)
			fc_context->glob->dependsOnRole = true;

		/* 收集此查询的rtable中的关系OID */
		foreach(fc_lc, fc_query->rtable)
		{
			RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_lc);

			if (fc_rte->rtekind == RTE_RELATION)
				fc_context->glob->relationOids =
					lappend_oid(fc_context->glob->relationOids, fc_rte->relid);
			else if (fc_rte->rtekind == RTE_NAMEDTUPLESTORE &&
					 OidIsValid(fc_rte->relid))
				fc_context->glob->relationOids =
					lappend_oid(fc_context->glob->relationOids,
								fc_rte->relid);
		}

		/* 并递归进入查询的子表达式 */
		return query_tree_walker(fc_query, extract_query_dependencies_walker,
								 (void *) fc_context, 0);
	}
	/* 提取函数依赖关系并检查 regclass Consts */
	fc_fix_expr_common(fc_context, fc_node);
	return expression_tree_walker(fc_node, extract_query_dependencies_walker,
								  (void *) fc_context);
}
